Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Tutoriel et Help

ASM Deamon

Nous veillons aux respects des droits d'auteur : si par erreur un contenu protégé est disponible sans indication sur ce site, merci de contacter l'administrateur, il sera retiré ou mis à jour dans les plus brefs délais.


Cours : L'Assembleur

L'assembleur

Introduction

Ce cours vous semblera sans doute long et pénible (ça l'est également pour moi pour l'écrire ;-) ) car ce n'est pas forcément facile de comprendre tout ça, mais efforcez vous à bien vous concentrer dessus car c'est important si vous voulez devenir un bon cracker. Attention, il ne s'agit pas non plus d'apprendre tout cela par coeur. Je vous conseille de prendre des notes et de les garder à côté de vous quand vous crackez ou sinon, pour les faignants, d'imprimer ce cours disponible au format imprimable sur le Guide PDF.

Dans ce cours vous allez apprendre les bases de l'assembleur. L'assembleur c'est le langage de programmation que vous voyez quand vous désassemblez un fichier dans WinDasm. C'est un langage assez proche du langage machine.

Sommaire :

1° Le processeur et les registres
 A) Les registres généraux ou de travail
 B) Les registres d'offset ou pointeur
C) Les registres de segment
D) Le registre Flag
 2° La Pile et ses instructions
A) Fonctionnement de la pile
 B) L'instruction PUSH
C) L'instruction POP
 3° Les sauts conditionnels et le CMP
A) Les sauts conditionnels
B) L'instruction CMP
 4° Les opérations mathématiques
A) Addition et Soustraction : ADD et SUB
 B) Multiplication : MUL / IMUL
 C) Division : DIV / IDIV
 D) Autres division et multiplication : SHR et SHL
E) L'opposé d'un nombre : NEG
5° Les nombres à virgule et les nombres négatifs
A) Les nombres à virgule
 B) Les nombres négatifs
6 ° Les instructions logiques
A) ET logique : AND
B) OU logique inclusif : OR
 C) OU logique exclusif : XOR
 D) Non logique : NOT
 E) TEST
7° La mémoire et ses instructions
A) LEA
 B) MOVSx
 C) STOSx
 8° Fin du cours !

1° Le processeur et les registres

Les données dont le processeur a besoin sont stockées dans des "cases" qu'on appelle des registres. Il existe plusieurs types de registres et chacun a son utilité.

  • - registres généraux ou de travail
    Ils servent à manipuler des données, à transférer des paramètres lors de l’appel de fonction DOS et à stocker des résultats intermédiaires.

  • - registres d’offset ou pointeur
    Ils contiennent une valeur représentant un offset à combiner avec une adresse de segment

  • - registres de segment
    Ils sont utilisés pour stocker l’adresse de début d’un segment. Il peut s’agir de l’adresse du début des instructions du programme, du début des données ou du début de la pile.

  • - registre de flag
    Il contient des bits qui ont chacun un rôle indicateur.

A) Les registres généraux ou de travail

Les 4 registres généraux les plus utilisés sont : AX , BX , CX et DX.

  • AX -- accumulateur -- sert à effectuer des calculs arithmétiques ou à envoyer un paramètre à une interruption

  • BX -- registre auxiliaire de base -- sert à effectuer des calculs arithmétiques ou bien des calculs sur les adresses

  • CX -- registre auxiliaire (compteur) -- sert généralement comme compteur dans des boucles

  • DX -- registre auxiliaire de données -- sert à stocker des données destinées à des fonctions

Ceci est leur utilisation théorique, mais dans la pratique ils peuvent être utilisés à d'autres usages.

Ces registres sont de 16 bits (petit rappel : 8 bits = 1 octet et 1 bit = 0 ou 1) et chacun de ses registres est divisé en 2 parties : L (comme Low pour désigner les bits de poids faible) et H (pour High afin de désigner les bits de poids forts). Par exemple : AX (AL et AH), BX (BL et BH), CX (CL et CH), DX (DL et DH).
Pour obtenir un registre de 32 bits il faut rajouter un "E" (pour Extended, en français "étendu") devant le registre. Par exemple EAX (32 bits) pour AX (16 bits), (EBX pour BX, ECX pour CX, EDX pour DX). Il existe également des EAH ou EAL, mais la partie haute d'un registre 32 bits ne sera pas directement accessible.

Les bits correspondent en fait à leur capacité : 8 bits = nombre entre 0 et 255 (en décimal) au maximum, 16 bits = 65535 au maximum, 32 bits = 4 294 967 295 au maximum.

EAX (32 bits)
 | AX (16 bits)
 | AH (8 bits) | AL (8 bits)

B) Les registres d'offset ou pointeur

Voici les différents registres :

  • IP -- Pointeur d'instruction -- est associé au registre de segment CS (CS :IP) pour indiquer la prochaine instruction à exécuter. Ce registre ne pourra jamais être modifié directement ; il sera modifié indirectement par les instructions de saut, par les sous-programmes et par les interruptions

  • SI -- Index de source -- est principalement utilisé lors d’opérations sur des chaînes de caractères ; il est associé au registre de segment DS.

  • DI -- Index de destination -- est principalement utilisé lors d’opérations sur des chaînes de caractères ; il est normalement associé au registre de segment DS ; dans le cas de manipulation de chaînes de caractères, il sera associé à ES
  • SP -- Pointeur de pile -- est associé au registre de segment SS (SS :SP) pour indiquer le dernier élément de la pile.
  • BP -- Pointeur de base -- est associé au registre de segment SS (SS :BP) pour accéder aux données de la pile lors d’appels de sous-programmes (CALL).

Comme les registres généraux, ces registres peuvent être étendues à 32 bits en ajoutant le "E" : (EIP, ESI, EDI, ESP, EBP). Par contre ils ne possèdent pas de partie 8 bits (il n' y aura pas de EIH ou ESL par exemple).

Les registres SI et DI sont le plus souvent employés pour les instructions de chaîne et pour accéder à des blocs en mémoire.

Ces registres (sauf IP) peuvent apparaître comme des opérandes dans toutes les opérations arithmétiques et logiques sur 16 bits

ESI (32 bits)

SI (16 bits)

C) Les registres de segment

Le processeur utilise des adresses pour stocker ou récupérer des données. L'adresse est composée de 2 parties de 32 bits (ou 16 bits selon le mode utilisé). La première est le segment et la deuxième est l'offset. L'adresse est présenté comme cela : segment:offset (par exemple : 0A000h:00000h). Voici les différents segments :

  • CS -- Registre segment de code -- indique l’adresse du début des instructions d’un programme ou d’une sous-routine

  • DS -- Registre segment de données -- contient l’adresse du début des données de vos programmes. Si votre programme utilise plusieurs segments de données, cette valeur devra être modifié durant son exécution

  • SS -- Registre segment de pile -- pointe sur une zone appelée la pile

  • ES -- Registre segment supplémentaire (Extra segment) -- est utilisé, par défaut, par certaines instructions de copie de bloc. En dehors de ces instructions, le programmeur est libre de l’utiliser comme il l’entend

  • FS -- Registre segment supplémentaire -- Rôle semblable à ES

  • GS -- Registre segment supplémentaire -- Rôle semblable à ES

D) Le registre Flag

Ce registre est un ensemble de 16 bits. Mais cet ensemble ne signifie rien, car les bits sont modifiés un par un. Et la modification d'un seul bit peut changer le comportement du programme. Les bits de cet ensemble sont appelés "indicateurs".

Bit 1 : CF
Bit 2 : 1
Bit 3 : PF
Bit 4 : 0
Bit 5 : AF
Bit 6 : 0
Bit 7 : ZF
Bit 8 : SF
Bit 9 : TF
Bit 10 : IF
Bit 11 : DF
Bit 12 : OF
Bit 13 : IOPL
Bit 14 : NT
Bit 15 : 0
Bit 16 : RF
Bit 17 : VM

Les bits 1, 5, 12, 13, 14, 15 de ce registre Flag de 16 bits ne sont pas utilisés.

Les instructions arithmétiques, logiques et de comparaison modifient la valeur des indicateurs.
Les instructions conditionnelles testent la valeur des indicateurs et agissent en fonction du résultat.

Voici à quoi les principaux indicateurs servent :

CF : Carry Flag - Indicateur de retenue

A la suite d'une opération, si le résultat est codé sur un nombre supérieur de bits, le bit de trop sera mis dans CF. Plusieurs instructions peuvent modifier son état : CLC pour le mettre à 0, STC à 1 et CMC inverse sa valeur.

PF : Parity Flag - Indicateur de parité

Egal à 1 si le nombre de bits d'une opérande (paramètre d'une instruction) est pair.

AF : Auxiliary carry Flag - Indicateur de retenue auxiliaire

Cet indicateur ressemble à CF.

ZF : Zero Flag - Indicateur zéro

Si le résultat d'une opération est nul (égal à 0) ZF passera à 1. Cet indicateur est souvent utilisé pour savoir si deux valeurs sont égales (en les soustrayant).

SF : Sign Flag - Indicateur de signe

SF passe à 1 quand le résultat est signé (négatif ou positif).

IF : Interruption Flag - Indicateur d'interruption

Il permet d'enlever la possibilité au processeur de contrôler les interruptions. Si IF = 1 les interruptions sont prises en compte et si IF = 0 elles sont ignorées. L'instruction STI met IF à 1 et CLI le met à 0.

DF : Direction Flag - Indicateur de direction

C'est ce Flag qui donne l'indication sur la manière de déplacer les pointeurs (références) lors des instructions de chaînes (soit positivement, soit négativement). STD le met à 1 et CLD à 0.

OF : Overflow Flag - Indicateur de dépassement

Il permet de trouver et de corriger certaines erreurs produites par des instructions mathématiques. Très utile pour éviter les plantages. Si OF=1 alors nous avons affaire à un Overflow.

Les autres indicateurs ne sont pas importants nous n'allons donc pas nous attarder dessus.

Petit exemple sur ces indicateurs avec une opération en binaire :

0 1 1 0
+ 0 1 0 1
--------
1 0 1 1
  • ZF=0 (le résultat 1011 n'est pas nul)
  • SF=1 (le signe du résultat est négatif)
  • CF=0 (il n'y a pas de retenue)

2° La Pile et ses instructions

A) Fonctionnement de la pile

La pile ("stack" en anglais) est un emplacement qui sert à stocker de petites données. Cette mémoire temporaire fonctionne comme une pile d'assiette : c'est-à-dire que la dernière assiette que vous empilez sera la première à être prise et la première assiette empilée (celle qui est tout en dessous) sera la dernière prise. J'espère avoir été clair dans cette métaphore. Pour prendre un vrai exemple, si je mets la valeur "A" dans la pile puis je mets "B" et après "C". Pour récupérer ces valeurs le processeur va d'abord prendre "C", puis "B" et ensuite "A". Là si vous ne comprenez toujours pas je ne peux plus rien pour vous !

Pour l'API GetDlgItemText, Windows a besoin de ces informations :

HWND hDlg, // (1) handle de la boîte de dialogue
int nIDDlgItem, // (2) identifiant de la boîte de dialogue
LPTSTR lpString, // (3) pointe vers le buffer pour le texte
int nMaxCount // (4) taille maximum de la chaîne

On peut donc mettre avant d'appeler cette fonction :

MOV EDI, [ESP+00000220] place l'handle de la boite de dialogue dans EDI
PUSH 00000100 push (4) taille maxi de la chaîne
PUSH 00406130 push (3) adresse du buffer pour le texte
PUSH 00000405 push (2) identificateur du contrôle
PUSH EDI push (1) hanlde de la boite de dialogue
CALL GetDlgItemText appelle la fonction

B) L'instruction PUSH

Vous avez pu voir cette instruction au dessus. Push signifie "pousser" en français. C'est avec cette instruction qu'on place une valeur sur la pile. La valeur "poussé" doit être de 16 ou 32 bits (souvent un registre) ou une valeur immédiate.

Par exemple :
PUSH AX
PUSH BX
PUSH 560
AX est placé en haut de la pile en premier mais ce sera 560 qui en ressortira en premier.

C) L'instruction POP

Mettre des données sur la pile c'est bien, mais le but c'est de les récupérer par la suite. C'est donc à ça que sert l'instruction POP : récupérer les données placées sur la pile.

Par exemple (suite à l'exemple au dessus) :
POP CX
POP BX
POP AX
Ces instructions mettront la première instruction du haut de la pile dans CX, c'est à dire 560. Et ensuite BX reviendra dans BX et AX dans AX.

La pile est très utilisée pour retrouver les valeurs intactes des registres. Car le nombre de registres est limité, donc on utilise la pile lorsque l'on n'a plus de place. Mais la pile est plus lente que les registres.

3° L es sauts conditionnels et le CMP

A) Les sauts conditionnels

Dans les différents sauts il y a les inconditionnels (qui sautent sans conditions) comme le JMP (Jump) et ceux qui sont conditionnels (qui sautent donc si la condition est vérifiée). Dans cette dernière "famille" il y a du monde. ;-) Les valeurs des indicateurs sont nécessaires pour la vérification de la condition.

Voici les principaux sauts, sachez que J = Jump if (Saut si) :

Indicateur | Valeur | Saut | Signification
CF | 1 | JB | Below - Inférieur
 |  | JBE | Below or Equal - Inférieur ou égal
 |  | JC | Carry - Retenue
 |  | JNAE | Not Above or Equal - Non supérieur ou égale
 | 0 | JA | Above - Supérieur
 |  | JAE | Above or Equal - Supérieur ou égal
 |  | JNB | Not Below - Non inférieur
 |  | JNC | Not Carry - Pas de retenue
ZF | 1 | JE | Equal - Egal
 |  | JNA | Not Above - Non supérieur
 |  | JZ | Zero - Zero (Saut en cas d'égalité)
 | 0 | JNBE | Not Below or Equal - Non supérieur ou égal
 |  | JNE | Not Equal - Non égal
 |  | JNZ | Not Zero - Non zéro (saut si différent)
PF | 1 | JP | Parity - Pair
 |  | JPE | Parity Even - Pair
 | 0 | JNP | Not Parity - Non pair
 |  | JPO | Parity Odd - Non pair
OF | 1 | JO | Overflow - Dépassement
 | 0 | JNO | Not Overflow - Pas de dépassement
SF | 1 | JS | Signed - Signé
 | 0 | JNS | Not Signed - Non signé

Et quelques autres :

JG - JNLE
Greater (arithmétiquement supérieur) - Not Less or Equal (arithmétiquement non inférieur ou égale)
ZF=0 et SF=OF

JGE - JNL
Greater or Equal (arithmétiquement supérieur ou égal) - Not Less (non inférieur arithmétiquement)
SF=OF

JL - JNGE
Less (arithmétiquement inférieur) - Not Greater or Equal (arithmétiquement non supérieur ou égal)
SF (signé)=OF

JLE - JNG
Less or Equal (arithmétiquement inférieur ou égal) - Not Greater (non supérieur arithmétiquement)
ZF=1 ou SF (signé)=OF

B) L'instruction CMP

Cette instruction permet d'effectuer des tests entre des valeurs et de modifier les flags suivant le résultat. Le fonctionnement du CMP est semblable au SUB (que l'on verra plus loin et qui sert pour la soustraction). Il s'utilise de cette façon : CMP AX, BX. Cela permet de vérifier si ces 2 valeurs sont égales car il fait AX-BX, si c'est égal à 0 alors ZF=1 sinon ZF=0.

Le plus souvent il est suivi de ces sauts et leur inverse (en rajoutant un "N" après le "J", par exemple JNE pour JE...) :

JA : plus grand que (nombres non-signés)
JB : plus petit que (nombres non-signés)
JG : plus grand que (nombres signés)
JL : plus petit que (nombres signés)
JE ou JZ : égal à (signé et non-signé)

4° L es opérations mathématiques

A) Addition et Soustraction : ADD et SUB

Ces 2 instructions nécessitent 2 opérandes : la source et la destination. Ce qui donnera : Destination = Source + Destination. Et en assembleur : ADD Destination, Source.
Exemple avec AX = 4 et BX = 6 :

ADD AX, BX

Après cette opération : AX = 4+6 = 10 et BX = 6

Le fonctionnement de SUB est identique.

Il est impossible d'ajouter ou soustraire un 16 bits avec un 8 bits par exemple. Donc ADD BX, CL sera impossible !

B) Multiplication : MUL / IMUL

L'instruction MUL est utilisé pour les nombres non signés et IMUL pour les signés (petit rappel : les nombres signés peuvent être négatifs contrairement aux non signés, par exemple en 16 bits en non signé les nombres vont de 0 à 65535 et en signé ils vont de -32768 à 32767). Contrairement aux opérations précédentes, la multiplication n'a besoin que d'une seule opérande : la source. La destination et donc le nombre qui devra être multiplié se trouvera toujours et obligatoirement dans AX. La source est un registre.

Exemple avec AX = 4 et BX = 6

MUL BX

Résultats : AX = 4*6 = 24 et BX = 6

IMUL fonctionne de la même façon, sauf qu'il faut utiliser l'instruction CWD (convert word to doubleword), qui va permettre "d'étendre" le signe de AX dans DX. Si cela n'est pas fait les résultats pourront être erronés.

C) Division : DIV / IDIV

Pareil que MUL et IMUL : DIV sert pour les nombres non signés et IDIV pour les non signés. Cette instruction divise la valeur de AX par la source.

Exemple avec AX = 18 et BX = 5 :

IDIV BX

Résultats : AX = 3 et DX = 3 (DX étant le reste)

D) Autre division et multiplication : SHR et SHL

Ces instructions permettent de diviser des registres avec SHR et les multiplier avec SHL. Ils demandent 2 opérandes : le nombre à diviser et le diviseur pour SHR et le nombre à multiplier et le multiplicateur pour SHL. Ils fonctionnent en puissance de 2. Donc si on veut diviser AX par 4 on mettra : SHR AX, 2 (car 2^2 = 4). Pour diviser BX par 16 on mettra : SHR BX,4 (car 2^4 = 16). Pour SHL c'est pareil : pour multiplier CX par 256 on mettra donc : SHL CX,8 (2^8 = 256). Pour multiplier ou diviser des nombres qui ne sont pas des puissances de 2 il faudra combiner des SHR ou SHL.
Petit rappel de mathématiques : x^0 donnera toujours 1 quelque soit x !

Exemple : on veut multiplier 5 par 384. On voit que 384 = 256 + 128, ce qui nous arrange car 256 et 128 sont des puissances de 2.

  • MOV AX,5 >> On met 5 dans AX >> AX = 5

  • MOV BX,AX >> On met AX dans BX >> BX = AX

  • SHL AX,8 >> On multiplie AX par 256 (2^8) >> AX = AX*256 = 5*256 = 1280

  • SHL BX,7 >> On multiplie BX par 128 (2^7) >> BX = BX*128 = 5*128 = 640

  • ADD AX,BX >> On ajoute BX à AX >> AX = AX + BX = 1280 + 640 = 1920

Et vous pouvez vérifier : 5*384 = 1920 ! ;-)

Mais vous me direz : "pourquoi ne pas faire simplement un MUL ?". Et bien car SHL est beaucoup plus rapide à exécuter. Mais pour des nombres qui ont une décomposition trop longue, mieux vaut bien sur utiliser un MUL.

E) L'opposé d'un nombre : NEG

L'instruction la plus simple à mon avis : NEG sert à rendre un nombre positif en négatif et inversement. Il ne demande qu'une seule opérande : la destination. Si AX = 5, alors un NEG AX mettra AX = -5. Ou si BX = -14 alors après le NEG BX, BX = 14.

5° L es nombres à virgule et les nombres négatifs

A) Les nombres à virgule

Le problème en assembleur c'est qu'on ne peut pas utiliser directement les nombres à virgule. Nous utiliserons les nombres à virgule fixe. Il s'agit simplement d'effectuer des calculs avec des valeurs assez grandes qui seront ensuite, redivisées pour retrouver un résultat dans un certain intervalle.

Par exemple nous voulons multiplier 20 par 0,25. Comme on ne peut pas mettre 0,25 dans un registre on va le mettre en nombre entier en le multipliant par 256 par exemple. 0.25*256 = 64. On le multipliera à 20 et ensuite ce résultat étant 256 fois trop grand on le redivisera par 256 :

  • MOV AX,64 >> On place 0,25*256 dans AX >> AX = 64

  • MOV BX,20 >> On place 20 dans BX >> BX = 20

  • MUL BX >> On multiplie AX par BX >> AX = AX*BX = 64*20 = 1280

  • SHR AX,8 >> On divise AX par 256 (2^8) >> AX = AX/256 = 1280/256 = 5

Le résultat de 20*0,25 est donc bien égal à 5.

Faîtes bien attention à ne pas effectuer de dépassement, surtout avec les nombres signés ou les grands nombres. En utilisant SHR ou SHL, plus l'opérande est grande et plus le résultat sera précis, car il y aura plus de décimales disponibles.

B) Les nombres négatifs

Un autre problème, c'est qu'en décimal pour un nombre négatif on place un "-" devant le nombre, mais en binaire on ne peut mettre que des 0 ou des 1. Pour pouvoir mettre un nombre en négatif on utilise la méthode du complément à 2. Voici en quoi cela consiste :

  • Convertir le nombre en binaire s'il ne l'est pas déjà

  • Inverser les bits du nombre (inverser les 0 en 1 et 1 en 0)

  • Ajouter 1 au nombre obtenu

Voici un exemple avec le nombre décimal 5 au format 8 bits (octet) :

  • On le convertit en binaire : 5(d) = 00000101(b)

  • On inverse : 00000101 --> 11111010 (ici le format, 8 bits, est important car en 4 bits par exemple cela donnera : 1010)

  • On ajoute 1 : 11111010 + 00000001 = 11111011

-5 est donc égal à 11111011 en binaire.

Pour que le nombre soit négatif il faut qu'il soit signé. En 8 bits en non signé les nombres vont de 0 à 255 et en signé ils vont de -128 à 127.

Quand le nombre est signé et en 8 bits, on peut savoir s'il est négatif en regardant le 8ème bit (en partant bien sur de la droite). S'il est égal à 1 alors le nombre est négatif. Pour les nombres à 4 bits c'est le 4ème qui compte.

Pour transformer -5 en hexadécimal, on décompose le nombre binaire en demi-octet (11111011 en 1111 et 1011). 1111b = 15d = Fh et 1011b = 11d = Ah. Donc -5d = FAh. (Petit rappel : d = décimal, b = binaire, h = hexadécimal, pour ceux qui n'auraient pas encore compris).

6° L es instructions logiques

Les opérations de ces instructions se font bit à bit. Et les opérandes et emplacements mémoires qu'elles utilisent sont en 8 ou 16 bits.

Petit rappel : pour convertir de tête un nombre binaire en décimal : chaque rang d'un nombre binaire est un multiple de 2.

2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1

Ainsi par exemple 10110110 donnera 128+32+16+4+2 = 182 :

1 | 0 | 1 | 1 | 0 | 1 | 1 | 0
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1

A) Et logique : AND

AND (ET) effectue un ET LOGIQUE sur 2 opérandes et le résultat se retrouvera dans la première opérande. Le ET LOGIQUE donne le résultat 1 uniquement quand les 2 opérandes sont 1, sinon il met 0.

opérande 1 | opérande 2 | AND
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1

Exemple avec AX = 15 et BX = 26 :

AND AX,BX

Résultat :

0000 1111 (AX=15)
AND 0001 1010 (BX=26)
-------------
0000 1010 (AX = 10)

B) Ou logique inclusif : OR

OR effectue un OU LOGIQUE INCLUSIF sur 2 opérandes et le résultat se retrouvera dans la première opérande. Le OU LOGIQUE INCLUSIF donne le résultat 0 uniquement quand les 2 opérandes sont 0, sinon il met 1.

opérande 1 | opérande 2 | OR
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 1

Exemple avec AX = 15 et BX = 26 :

OR AX,BX

Résultat :

0000 1111 (AX=15)
OR 0001 1010 (BX=26)
-------------
0001 1111 (AX = 31)

C) Ou logique exclusif : XOR

XOR effectue un OU LOGIQUE EXCLUSIF sur 2 opérandes et le résultat se retrouvera dans la première opérande. Le OU LOGIQUE EXCLUSIF donne le résultat 1 quand les 2 opérandes sont différentes, sinon il met 0.

opérande 1 | opérande 2 | XOR
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0

Exemple avec AX = 15 et BX = 26 :

XOR AX,BX

Résultat :

0000 1111 (AX=15)
XOR 0001 1010 (BX=26)
-------------
0001 0101 (AX = 21)

D) Non logique : NOT

NOT effectue un NON LOGIQUE sur une opérande et le résultat se retrouvera dans l'opérande. Le NON LOGIQUE inverse la valeur de chaque bit.

opérande | NOT
0 | 1
1 | 0

Exemple avec AX = 15 :

NOT AX

Résultat :

NOT 0000 1111 (AX=15)
-------------
1111 0000 (AX = -16 en signé et 240 en non signé)

E) TEST

Cette instruction teste la valeur d’un ou plusieurs bits en effectuant un ET LOGIQUE sur les opérandes. Le résultat ne modifie pas les opérandes mais les indicateurs.

Exemple :

TEST AX,1 >> Effectue un ET LOGIQUE sur le premier bit de AX avec 1, si il est égal à 1, ZF passera à 1 et inversement (si 0 alors ZF=0).

Un TEST AX,AX (souvent utilisé) permet de voir si AX est égal à 0 ou non.

7° La mémoire et ses instructions

A) LEA

Pour placer par exemple la valeur de l'offset de MESSAGE dans SI il suffit de mettre : MOV SI,Offset-Message. Mais on ne peut pas procéder de cette façon pour les registres DS, ES, FS, GS car ceux ci n'acceptent pas les valeurs numériques, ils n'acceptent que les registres. Il faudra donc passer par : MOV AX,Seg-Message puis MOV DS,AX.

Il existe une autre instruction, LEA, qui permet de placer l'offset dans un registre mais en plus court. Par exemple : LEA SI,MESSAGE placera dans SI l'offset de Message. L'utilisation de LEA à la place de MOV rend le code plus clair et plus compact.

B) MOVSx

Pour déplacer des blocs entiers de mémoires il faut utiliser les instructions : MOVSB, MOVSW ou MOVSD selon le nombre de bits à déplacer.

  • MOVSB : (B) = Byte (Octet : 8 bits) (Attention !! En anglais "byte"= un octet et "bit"= un bit)
  • MOVSW : (W) = Word (Mot : 16 bits)
  • MOVSD : (D) = DWord (Double Mot : 32 bits)

Si on veut déplacer 1000 octets (bytes) en utilisant la commande MOVSB il faudra qu'elle se répète 1000 fois. Donc l'instruction REP est utilisée comme une boucle. Il faut placer auparavant le nombre de boucles à effectuer dans le registre de compteur (CX) :

MOV CX,1000 >> Nombre de boucles à effectuer dans le compteur CX
REP MOVSB >> Déplace un octet

Pour aller plus vite on peut les déplacer par Mot (Word) :

MOV CX,500 >> 1000 bytes = 500 words
REP MOVSW >> Déplace un mot

Ou alors en Double Mot (DWord) :

MOV CX,250 >> 1000 bytes = 500 words = 250 dwords
REP MOVSD >> Déplace un Double Mot

A chaque MOVSx, DI augmente de 1,2 ou 4 bytes

C) STOSx

Pour garder des données ou les placer dans un emplacement de la mémoire on utilise les instructions STOSB, STOSW ou STOSD.

Pareil que les MOVSx, ces instructions servent respectivement à stocker un byte, un word et un dword. Par contre ils utilisent AL, AX (EAX 32bits) comme valeur à stocker. La valeur de AL,AX,EAX sera stockée dans ES:DI. A chaque STOSx, DI est augmenté de 1,2 ou 4 bytes.

Exemple :

MOV CX,10 >> nombre de répétitions placées dans CX, ici 10
MOV AX,0 >> valeur à déplacer, ici 0
REP STOSB >> stocker AX dans ES:DI

Nous aurons donc 10 bytes de 0 dans ES:DI.

8° Fin du cours !

Voilà c'est la fin du cours ! "Ouf" diront certains, "Oh quel dommage" diront les hypocrites ! ;-) Je félicite ceux qui ont réussi à lire tout ça ! ;-) J'espère que cela vous servira à mieux comprendre en crackant. Avec ça vous devriez vous sentir plus à l'aise (enfin si vous avez compris mon cours).

J'espère avoir été le plus compréhensible possible. Mais si vous ne comprenez pas quelque chose à propos de ce cours demandez sur le forum plutôt que de m'envoyer un mail sur deamon.crack (at) netcourrier.com car j'essaye de toute façon de lire tous les posts du forum et d'autres personnes que moi pourront vous aider.

Je voulais féliciter le très bon cours de Falcon sur l'assembleur qui m'a permis de découvrir l'assembleur. D'ailleurs j'ai repris un peu son plan, car je n'ai pas réussi à en faire un bien, et j'ai dû copier seulement 2-3 phrases. Mais c'est bien moi qui ai écrit tout ça, en 6-8 heures (et oui c'est très long à écrire tout ça) !

Pendant que je suis là ;-) , je remercie également tout ceux qui viennent sur mon site, ceux qui m'envoient des mails de sympathie et ceux qui participent plus ou moins activement sur le forum.

Deamon, le 15-16 juillet 2003


Les bases de l'asm

Introduction

Quoi de plus ardu que d'essayer de résumer l'asm pour n'en garder que l'essentiel. De plus, comment voulez-vous posséder un bon niveau de programmeur mais aussi d'analyste si des instructions vous font défaut? D'un autre côté, rien ne sert de se leurrer, peu sont ceux qui peuvent se vanter de connaître toutes les instructions sur le bout des doigts ainsi que toutes les astuces qui en découlent. C'est pourquoi je ne perdrais pas ici mon temps à détailler toutes les instructions ni à faire du plagia (en cherchant un peu, Google regorge de tutos plus ou moins bien expliqués et complets sur l'asm). Je vais simplement présenter brièvement l'asm et faire un bref rappel de quelques instructions qui me semblent intéressantes pour le cracking mais sans rentrer dans les détails techniques, ainsi que de quelques astuces utilisées avec celles-ci. Libre à vous de continuer votre apprentissage sur d'autres sites.

Les registres

Les registres principaux sont : EAX, EBX,ECX, EDX, EDI, ESI, EBP, ESP, EIP et les Flags.

Ce sont des petites zones de stockage logées dans le processeur auxquels il a un accès très rapide (beaucoup plus rapide qu'en mémoire).

Ils ont tous une taille de 32 bits (4 bytes).

EAX, EBX, ECX et EDX sont les registres de travail. La partie basse (les 16bits de poids fort) de chacun d'eux peut être utilisée directement mais reste une partie du registre de 32bits, c'est à dire que s'il l'on modifie AX (la partie basse de EAX), on modifiera EAX. En plus de cela, la partie basse peut être de nouveau séparée en 2 parties de 8bits (la partie haute AH et la partie basse AL pour AX, BH et BL pour BX, ...). De nouveau si l'on modifie l'une des 2 parties, c'est tout le registre qui est modifié (AX est modifié donc EAX aussi).

EDI et ESI servent principalement pour des instructions d'adressage (lors de traitements de chaînes de caractères par exemple) mais peuvent aussi servir de registres de travail.

EBP et ESP sont des registres de pile, ESP désigne toujours le sommet de la pile. EBP permet de travailler dans la pile sans devoir utiliser ESP qu'il ne vaut mieux pas modifier.

EIP désigne l'adresse de l'instruction courante.

Le registre de Flags est un registre dont chaque bit (indicateur) indique un état. Les instructions arithmétiques, logiques et de comparaison modifient la valeur des indicateurs.

La pile

La pile est un espace mémoire régit par le système LIFO (Last In First Out). La dernière valeur à avoir été empilée sera la première à être dépilée. Imaginez une pile d'assiettes : la dernière que vous avez déposée sur le paquet sera celle que vous reprendrez en première, c'est le principe de la pile. On y accède par des PUSH et des POP

Quelques instructions courantes

NOP (90h)

La plus simple à expliquer : cette instruction ne fait rien! Vous le savez peut-être mais nous ne pouvons modifier la taille d'un programme sans des conséquences désastreuses. C'est là que cette instruction prend toute son utilité. Elle permet de supprimer une instructions sans modifier la taille du programme (si l'instruction à "effacer" tient sur plusieurs bytes remplacez autant de bytes que nécessaires par NOP).

MOV destination, source

Cette instruction copie la valeur de source dans destination. Les 2 opérandes doivent avoir la même taille. Il n'y peut y avoir au maximum qu'un accès mémoire par instruction. Si vous voulez par exemple mettre la valeur de [00401234] dans [00405678], vous devrez faire par exemple:
MOV EBX , [401234]

MOV [405678] , EBX

mov eax , 45 ; Mettre 45 dans eax

ADD destination, source

Cette instruction additionne le contenu de destination à celui de source. Le résultat est placé dans destination.

mov eax,45

add eax,10 ; eax <= eax (45) + 10 donc eax vaudra 55

SUB destination, source

Cette instruction soustrait le contenu de source à celui de destination. Le résultat est placé dans destination.

mov eax,45

sub eax,5 ; eax <= eax (45) - 5 donc eax vaudra 40

INC destination

Cette instruction incrémente destination d'une unité

mov eax,10

inc eax ; eax <= eax + 1

DEC destination

Cette instruction décrémente destination d'une unité

mov eax,10

dec eax ; eax <= eax - 1

JMP label

Modifie l'EIP en l'adresse désignée par label. Ce que l'on désigne par un saut.

jmp label

; instructions non exécutées

label:

; instructions à éxécuter

PUSH source

Place la valeur de source au sommet de la pile.

POP destination

Place la valeur au sommet de la pile dans destination.

mov eax,15

push eax ; Place la valeur de eax (15) sur la pile

pop ebx ; Récupère la valeur au sommet de la pile (15, c'est celle que l'on vient de mettre) et la place dans ebx

CMP destination, source

Cette instruction effectue la soustraction de destination et source mais ne conserve pas le résultat. Seuls les Flags sont modifiés.

Le ZF (Zero Flag) est positionné à 1 si le résultat de la soustraction est nul (donc que les 2 valeurs sont égales).

Le CF (Carry Flag) est positionné à 1 si le résultat ne tient pas sur 32bits (donc que le résultat est est inférieur à 0).

Le SF (Sign Flag) est positionné à 1 si le résultat est négatif.

Le OF (Overflow Flag) est positionné à 1 si le résultat a provoqué un débordement (lorsqu'on travaille avec des nombres signés).

mov eax,5

cmp eax,10 ; eax < à 10 => ZF=0, SF=1 et CF=1

cmp eax,2 ; eax > à 2 => ZF=0, SF=0 et CF=0

cmp eax,5 ; eax = 5 => ZF=1, SF=0 et CF=0

Les sauts conditionnels

Fonctionne comme un JMP mais saute en fonction de l'état de certains bits du registre de Flags.

Les différents sauts sont :

Mnémo | Saut si … | Indicat. testés
JA | Supérieur | CF = 0 et ZF = 0
JAE | Supérieur ou égal | CF = 0
JB | Inférieur | CF = 1
JBE | Inférieur ou égal | CF = 1 ou ZF = 1
JC | CF = 1 | CF = 1
JCXZ | Registre CX à 0 | CX = 0
JE | Egal | ZF = 1
JG* | Supérieur | ZF = 0 et SF = OF
JGE* | Supérieur ou égal | SF = OF
JL* | Inférieur | SF<>OF
JLE* | Inférieur ou égal | ZF = 1 ou SF<>OF
JNA | Pas supérieur | CF = 1 ou ZF = 1
JNAE | Pas supérieur ni égal | CF = 1
JNB | Pas inférieur | CF = 0
JNBE | Pas inférieur ni égal | CF = 0 et ZF = 0
JNC | CF = 0 | CF = 0
JNE | Pas égal | ZF = 0
JNG* | Pas supérieur | ZF = 1 et SF<>OF
JNGE* | Pas supérieur ni égal | SF<>OF
JNL* | Pas inférieur | SF = OF
JNLE* | Pas inférieur ni égal | ZF = 0 et SF = OF
JNO | OF = 0 | OF = 0
JNP | PF=0 | PF = 0
JNS* | SF=0 | SF = 0
JNZ | ZF=0 | ZF = 0
JO* | OF=1 | OF = 1
JP | PF=1 | PF = 1
JPE | Parité paire | PF = 1
JPO | Parité impaire | PF = 0

* Utiliser avec des nombres signés

mov eax,2

cmp eax,5

je lblfin ; On ne saute pas puisque 2<>5

; Instructions qui vont être exécutées puisque eax <> 5 et donc on ne sautera pas

lblfin:

Les opérations logiques

Les opérations logiques travaillent toutes bit à bit. Elle peuvent servir à comparer deux valeurs, tester certains bits mais aussi à forcer certains bits à une certaine valeur.

AND destination, source

Le AND effectue un ET LOGIQUE de destination et source. Le AND peut se traduire par une multiplication de bits.

Voici la table logique :

A | B | AND
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1

mov al,14h ; 00010100b

mov bl,2Fh ; 00101111b

and al,bl ; 00000100b = 04h => al va prendre la valeur 04h

OR destination, source

Le OR effectue un OU inclusif de destination et source. Le OR peut se traduire par une addition de bits sans report.

A | B | OR
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 1

mov al,14h ; 00010100b

mov bl,2Fh ; 00101111b

or al,bl ; 00111111b = 3Fh => al va prendre la valeur 3Fh

XOR destination, source

Le XOR effectue un OU inclusif de destination et source. Le XOR peut se traduire par une addition de bits avec report.

A | B | XOR
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0

mov al,14h ; 00010100b

mov bl,2Fh ; 00101111b

xor al,bl ; 00111011b = 3Bh => al va prendre la valeur 3Bh

Voilà, ce n'est évidemment pas complet mais j'espère que cela vous permettra de commencer mes cours sans trop galérer.

Si vous voulez compléter votre apprentissage, et je vous conseille de le faire quitte à garder les liens et à les utiliser par après, voici quelques sites non exhaustifs qui vous permettrons d'apprendre les bases (plus ou moins par ordre de difficulté) :

http://benoit-m.developpez.com/assembleur/tutoriel | Les bases de l'asm
http://asm.developpez.com/intro/index.php | Aussi
http://www.developpez.com/ | Un site regroupant les bases de beaucoup de langages
http://www.deamoncrack.host.sk/Pages/cours-asm.htm | Si vous n'avez pas trop de temps et que vous avez juste besoin d'un rappel
http://www.chez.com/pageasm/ | Intro à la programmation win32 mais aussi DOS
http://win32assembly.online.fr/ | Le site de référence pour la programmation win32 mais en anglais
les opcodes | Très complet mais en anglais

Bon crack,

Crisanar

Dernière mise à jour le 26/03/2016 16:26


Introduction à OllyDBG

Ce dont vous avez besoin : OllyDBG

OllyDbg est un analyser, debugger et assembleur 32 bits avec une interface intuitive. Il est bien plus puissant que W32DASM et ses plugins lui permettent même de rivaliser avec le grand SoftIce.

Voici à quoi ressemble une fenêtre d'un programme désassemblé :

Sachez que grâce aux icônes que vous voyez ici(1) et au clic droit, vous avez accès à la quasi totalité des fonctions d'Olly.

Je vais brièvement expliquer les différentes fonction d'Olly qui me semble intéressantes. Sachez qu'il en existe bien d'autres qui ont aussi leur utilité.

(1) : ToolBar

(Certaines fonctions ne servent pratiquement jamais, je ne les expliquerai pas ici)

(F3) sert à ouvrir un fichier à désassembler

(CTRL+F2) sert à recharger le dernier programme ouvert (s'il est en cours d'exécution, il se ferme puis se recharge)

(ALT+F2) sert à arrêter le programme en cours d'exécution

(F9) sert à exécuter le programme (au cas où il est en pause sur un BP - Break Point - par exemple)

(F12) sert à mettre le programme en pause (chez moi le programme plante lorsque je clique dessus)

(F7) sert à rentrer dans une fonction lors de l'exécution en pas à pas

(F8) sert à exécuter les fonctions sans rentrer dedans en pas à pas

(CTRL+F9) sert à exécuter le programme jusqu'à ce qu'il y ait une instruction RET (pratique pour sortir d'une fonction)

sert à se positionner à une certaine adresse dans le programme

sert à afficher les chaînes de caractères trouvées lors de l'analyse du programme (il faut toutefois faire un clic droit>Search for>All referenced text strings avant de pouvoir voir quoique se soit)

(2) Offsets

Position dans le programme. Pour voir les offsets réels (ceux dans le fichier exécutable) clic droit>View>Executable File

Pour ajouter un label sur une ligne, appuyez sur ':' puis entrez le nom de votre label.

(3) Code hexadécimal

Ici est affiché la valeur en hexa des instructions en asm correspondantes. Si vous double cliquez dessus, vous posez un BP sur la ligne correspondante.

(4) Instructions assembleur

Si vous double cliquez sur une ligne, vous pourrez éditer l'instruction sélectionnée. Attention, le patch se fait en mémoire. Si vous voulez conserver les changements : copy to executable>all modifications, vous fermez la fenêtre qui s'ouvre et vous enregistrez.

(5) Analyse d'Olly / commentaires

Ici se trouve les fonctions reconnues par Olly ainsi que leurs arguments et/ou vos commentaires. Pour entrez un commentaire, appuyez sur ';' et entrez le texte de votre commentaire.

(6) Registres généraux

Ici se trouvent les registres. La couleur rouge indique qu'ils ont été modifié à l'instruction précédente.

(7) Flags

Les flags peuvent être modifiés en double-cliquant dessus.

(8) Fenêtre d'état

Ici se trouve des informations complémentaires lors de l'exécution pas à pas, comme la valeur d'une variable, si un saut est pris ou pas,...

(9) Fenêtre de Dump

Vous voyez ici le code hexadécimal d'une partie de l'exécutable en mémoire. Pour aller à une position précise : clic droit dans cette fenêtre>Goto>Expression et vous entrez l'adresse. Vous pouvez ici aussi donner un label à une adresse. Pour cela vous vous positionner à la bonne adresse puis vous appuyez sur ':' et vous entrez le label.

(10) Fenêtre de la pile

(11) Info complémentaires

Ici sont affichée des infos comme une erreur qui s'est produite,le nombre de fonctions trouvées lors de l'analyse, ...

(12) Etat

Indique l'état du programme (PAUSED, RUNNING, TERMINATED)

Le clic droit

Le clic droit vous donne accès à de nombreuses fonctions. Celle que je n'ai pas encore citée ici et qui pourra vous servir est Goto>Expression. Vous rentrez l'adresse à laquelle vous désirez vous rendre et il vous y amène.

Autres astuces

Si vous utilisez un éditeur hexa, vous regretterez qu'Olly n'affiche pas l'offset réel dans l'exe d'une instruction. Effectivement, il ne l'affiche pas directement mais si vous faites clic droit>View>Executable File, vous aurez une nouvelle fenêtre qui s'affichera avec les offsets réels dans le fichier exe.

Voilà, maintenant vous êtes prêts à vous attaquer à votre premier programme.

Bon crack,

Crisanar

Dernière mise à jour le 26/03/2016 16:24


Cours n°1 - Adieu YouNAG

Bienvenue dans la cité du crack! ;)

J'utiliserai au début des crackmes, c'est-à-dire des programmes prévus exclusivement pour être crackés. Cela évite les problèmes de droits d'auteurs et permet de cibler plus facilement la protection ;) .

Ce dont vous avez besoin : Olly, PEID, LordPE (prenez la version deluxe par yoda + mise à jour) et le crackme : UnNagMe1

Certains programmes utilisent comme "protection" ce que l'on appelle un NAG screen, c'est à dire un message indésirable qui s'affiche par exemple au démarrage du programme pour nous rappeler de nous enregistrer ou d'autres choses désagréables du genre.

Analyse du logiciel

Certaines étapes du cracking doivent devenir un réflexe. L'analyse du logiciel en est une.

Nous ouvrons donc PEID, puis nous chargeons notre UnNagMe1 soit par glisser-deposer soit par le bouton ouvrir (1) .

Si la signature d'un compilateur ou d'une protection est trouvée, PEID l'affichera (2)

Dans notre cas PEID ne trouve rien. En fait, le programme est tellement petit qu'il ne trouve pas de signature connue.

Ce n'est pas grave, prenons Olly et voyons de quoi il retourne.

Lancez Olly, ouvrez l'UnNagMe en cliquant sur la petite icône représentant un dossier

Nous pouvons maintenant commencer l'observation.

Observation

Si nous lancons l'exécutable, nous voyons que nous avons deux MessageBox. Une NAG et une OK. Il ne faut donc garder que la 2eme.

Analyse du code

Grâce à Olly, nous voyons directement les appels aux deux MessageBox.

MessageBox est une API Windows qui permet d'afficher une petite fenêtre préfabriquée pour afficher des messages d'informations comme un message d'erreur par exemple.

Voici la définition de l'API MessageBox (vous pouvez aller voir vous-même dans l'API-Guide ou le W32 SDK Reference pour plus d'infos)

int MessageBox(
HWND hWnd, // handle de la fenetre propriétaire
LPCTSTR lpText, // adresse du texte de la message box
LPCTSTR lpCaption, // adresse du titre de la message box
UINT uType // style de la message box
);

En asm, les paramètres sont passés à l'envers. Retenez bien cela car vous y aurez souvent affaire. Grâce à Olly, vous voyez bien à quoi correspondent ces paramètres.

Patching

Pour ne pas afficher la première MessageBox (le NAG) plusieures solutions s'offrent à nous :

¤ Nopper le CALL de la première MessageBox

Pour cela, cliquez sur la ligne du CALL en 40100E, ensuite tapez "nop" (qui signifie no operation en asm, c'est à dire on ne fait rien!), vérifiez que la case "Fill with NOP's" est bien cochée(cette case sert à remplir les bytes qui resteraient avec des NOPs) et cliquez sur "Assemble". En noppant le CALL (en le remplaçant par des NOP si vous préférez ;) ) , on n'appelle plus la fonction pour afficher la MessageBox et donc elle ne s'affiche plus. Cette solution n'est pas très élégante vu que les PUSHs des paramètres de la MessageBox sont toujours dans la pile et ne seront pas dépilés. Dans un programme comme celui-ci ça n'aura pas de conséquence mais dans un programme un peu plus important cela pourrait être désastreux, imaginez qu'on ait poussé quelque chose dans la pile pour le récupérer après, on ne va plus récupérer notre valeur mais le dernier paramètre de la MessageBox! Il faut donc nopper les 4 PUSHs de paramètres(le style, le titre, le message et le propriétaire de la messagebox) si on ne veut pas avoir de problèmes.

¤ Sauter à la bonne MessageBox

A l'EP (Entry Point, endroit où démarre le programme - ici en 401000), plutot que d'afficher le NAG, on va sauter directement à la bonne MessageBox. Mais attention, la deuxième MessageBox commence avec son premier paramètre en 401013. Nous allons donc écrire JMP 401013 en 401000 (L'EP). Nous écrasons ainsi des données mais ce n'est pas grave puisqu'elles ne serviront plus. Au final nous avons ceci(en rouge ce qui a été modifié) :

Si vous voulez enregistrer les changements (Olly modifie en mémoire seulement) : clic droit > Copy to executable>All modifications>Copy All. Une nouvelle fenêtre s'ouvre, fermez-la et enregistrez.

¤ Modifier l'EP

C'est pour moi la meilleure méthode. Si on dit au programme de démarrer non plus en 401000 mais en 401013, il n'affichera plus le NAG.

Pour faire cela, je préfère utiliser LordPE qu'un éditeur hexa, il y a moins de risques de se tromper.

Pensez à fermer Olly et tous les programmes qui pourraient utiliser l'UnNagMe sinon vous ne pourrez pas le modifier.

On lance LordPE, on clique sur PE Editor et on choisit notre UnNagMe.

LordPE nous affiche une quantité d'informations sur l'exe mais nous n'allons ici nous intéresser qu'aux deux premières : EntryPoint et ImageBase. Notre EP dans le programme était de 401000 ce qui est en fait EntryPoint+ImageBase. Nous voulons démarrer en 401013 donc il suffit de changer la valeur 1000 de l'EntryPoint en 1013. Cliquez sur Save et le tour est joué!

Voilà vous pouvez être fier de vous, vous avez déjà pas mal travaillé ;) !

Bon crack,

Crisanar

Dernière mise à jour le 26/03/2016 16:26


Cours n°2 - Y-a-t'il un debuggeur à bord?

Ce dont vous avez besoin : Olly, PEID et le crackme : UnNagMe2

Autant vous prévenir tout de suite : les programmeurs savent que l'on cracke leurs programmes et ils usent de toutes les méthodes possibles pour empêcher celà! Les salauds!

Je vais vous montrer ici un exemple très simple (mais alors très très simple) d'une méthode d'anti-debug.

Juste pour vous montrer ce que ça donne, lancez le programme normalement => une MessageBox "OK".

Maintenant ouvrez le avec Olly et appuyez sur F9 (Play) => une MessageBox "NAG"!

Magique? Meuh non, simplement anti-debique (oui j'ai honte parfois :p ).

Analyse du logiciel

PEID nous apprend : rien! Faut pas croire, c'est pas toujours comme ça! Mais il faut vraiment que ça devienne une habitude avant de commencer l'analyse du code.

Analyse du code

Voilons un peu ce code :

On voit un appel que nous connaissons maintenant à l'API MessageBox (étrangement nous ne voyons que le bon message, pas le NAG, mais j'expliquerai ça plus tard), juste au dessus un peu de code que nous analyserons après mais surtout un appel à l'API IsDebuggerPresent. Pour ceux qui ne comprennent pas l'Anglais et dont le cerveau n'est plus très éveillé (le cracking se pratique surtout en heures tardives, voire matinales ;) ) cette API regarde si le programme tourne dans un environnement de debugging.

Les API Windows, lorsqu'elles retournent une valeur, la placent dans eax.

L'API IsDebuggerPresent retourne 0 dans eax si le programme ne tourne pas dans un environnement de debugging et autre chose que 0 dans le cas contraire.

Regardons la ligne suivante :

OR eax,eax

JE short UnNagMe2.0040101E

Si vous avez bien révisé, vous savez que OR 0,0=0 et OR 1,1=1. Ici en faisant un OR de eax avec lui même, on ne modifie pas eax (grâce à la règle que je viens de citer) mais on modifie les flags. Celui qui nous intéresse ici est le Zero Flag (ZF) qui sera positionné à 1 si eax=0 et à 0 si eax != 0. Vous me direz "Mais pourquoi ne pas faire un CMP alors?", tout simplement parce qu'un OR prend moins de place qu'un CMP et est plus rapide (même si maintenant ce n'est plus vrai je pense).

Donc pour résumer, s'il n'y a pas de débuggeur, eax=0, donc le OR eax,eax va positionner le ZF et le saut conditionnel JE(=JZ Jump if Zero) va nous faire sauter en 0040101E où on va afficher la MessageBox "OK".

Regardons maintenant ce qui se passe si on ne saute pas, donc s'il y a un débuggeur.

Nous avons trois XOR, mais ce qui est important c'est ce qui est xorré. C'est une adresse mémoire et si vous regardez l'adresse du texte de la MsgBox, c'est justement la même!

Si vous passez en pas à pas(F8), vous verrez que ces trois XORs modifient le "OK" en "NAG".

Patching

Bon ici c'est vraiment pour dire puisque le programme "fonctionne" si on le lance normalement.

De nouveau pour ce crackme, plusieures manières de faire. J'ai choisi celle qui me semble la plus simple et qui ne modifie qu'un byte.

On a vu que l'on sautait ou pas en fonction du débuggeur, il suffit donc de sauter dans tous les cas et ça sera bon!

Pour cela, double-cliquez sur la ligne du JE (00401007) et remplacez le JE en JMP, cliquez sur Assemble et c'est fait! :D

Bon crack,

Crisanar

Dernière mise à jour le 26/03/2016 16:28


Cours n°3 - Vous aimez la pêche?

Ce dont vous avez besoin : Olly, PEID et le crackme : FishMe1

Analyse du logiciel

PEID ne trouve toujours rien, ce n'est pas grave de toute façon c'est encore de l'assembleur.

Observation

On lance notre petit crackme. Une zone de texte et un bouton pour vérifier qui est désactivé. Bon dans des cas comme celui-ci, soit le serial est unique soit il est calculé grâce à quelque chose d'extérieur au programme (Nom de l'utilisateur du PC par exemple).

Analyse du code

Je n'ai pas codé la protection la plus difficile qu'il soit, loin de là. Ici la difficulté réside surtout dans le fait qu'il y ait plus de code que dans les crackmes précédents. En effet, une gestion de fenêtre demande plus de programmation qu'une simple MsgBox. Mais on est encore loin de ce à quoi peut ressembler un programme écrit en VB par exemple!

Je me doute que vous n'avez pas lu tous les liens que j'ai proposé dans l'introduction, c'est pourquoi je vais vite faire un rappel sur les dialogbox.

L'apparence des dialogbox est créée à partir d'un fichier ressource (.rc), que l'on peut facilement compiler grâce à un éditeur de ressources comme ResEd.

Grâce aux dialogbox, c'est Windows qui gère toute l'initialisation des contrôles (boutons, zone de texte,...) de notre fenêtre (d'autres choses diffèrent aussi mais je ne connais pas tout).

Lorsqu'un évènement se produit sur la fenêtre (clic, déplacement, dimensionnement, ...) Windows envoie un message à la procédure de notre fenêtre, indiquant l'événement qui s'est produit ainsi que les paramètres de cet évènement (par exemple lors d'un clic, il envoie l'ID du contrôle cliqué en paramètre).

Les événements non gérés par notre procédure sont gérés par Windows (à condition de retourner FALSE dans eax).

Enfin tout ceci ne vous concerne pas vraiment mais en sachant ce qui est nécessaire à la gestion d'une dialogbox, il est plus facile de comprendre ce qui concerne le test du serial ou pas.

Si vous voulez vraiment en savoir plus, lisez les tutos d'Iczelion. Sinon voici une source d'une dialogbox commentée. Regardez à la fois la source et l'exécutable (dans Olly) pour bien voir la structure de base d'une dialogbox (une fenêtre "normale" n'a pas une structure totalement différente).

Bon revenons à nos moutons. C'est surtout le cracking qui vous intéresse je suppose alors c'est partit pour le serial Fishing ;).

Ici pas question d'exécuter en pas à pas, vous allez rentrer dans les APis et vous n'arriverez à rien. Travaillons logiquement!

Si vous avez un peu analysé la source que j'ai fournie, vous avez vu que lors de l'appel de l'API DialogBoxParam, on fournissait aussi l'adresse de la procédure liée à la dialogbox.

Nous savons donc que notre procédure se trouve en 00401028.

Si nous regardons en 401028, nous voyons que mis à pars les constantes qui ont été remplacée par leurs valeurs effectives et les .IF, .ELSEIF,... qui ont été remplacé par des CMP... JE, JZ, ... nous retrouvons une structure semblable à celle que je vous ai donné dans la source.

En jetant un coup d'oeil rapide aux définitions des APIs qu'Olly nous fournit et en gardant en tête qu'un bouton est désactivé, nous voyons des choses intéressantes:

(1) Une msgbox qui nous indique qu'on à réussi.

Si vous avez besoin d'infos lisez les cours précédents.

(2) Une fonction qui récupère un texte d'un contrôle.

¤ Count = le nombre de caractères qui seront récupérés (souvent la taille du buffer)

¤ Buffer = adresse du buffer (Où va être enregistré le texte qui se trouve dans la zone de texte)

¤ ControlID = ID du contrôle dont on veut récupérer le texte (son nom en gros, pour trouver l'ID d'un contrôle j'expliquerai après)

¤ hWnd = handle de notre dialogbox (qui permet de la distinguer parmi les autres fenêtres)

S'il n'y a pas eu de problèmes, elle retourne dans eax la longueur du texte récupéré.

(3) Une fonction qui active ou désactive un contrôle (Pour Windows, une fenêtre ou un contrôle est une fenêtre).

¤ Enable = valeur booléenne (qui ne prend que 2 valeurs : VRAI ou FAUX) qui indique si le contrôle est disponible ou pas.

¤ hWnd = handle du contrôle à rendre disponible ou pas

Pour voir un peu mieux ce qui se passe posons un BP (Break point en appuyant sur F2) juste en dessous du Call GetDlgItemTextA en 004010BC. J'ai posé mon BP ici parce que c'est le point de départ. On ne saurait pas tester un serial sans l'avoir récupéré de la zone de texte! Appuyez sur F9 (Play). Cherchez la fenêtre (elle n'est pas dans la barre des tâches) et appuyez sur '1' par exemple. Attention Olly va breaker directement donc vous ne saurez rentrer qu'un caractère à la fois. Ceci nous apprend que la fonction GetDlgItemText est appelée à chaque fois que l'on rentre un caractère dans la zone de texte. Ça pourrait être aussi la fenêtre qui renvoie chaque frappe du clavier mais comme on teste l'ID de la zone de texte (CMP AX,0C9 en 40109F) avant d'appeler GetDlgItemText c'est donc la zone de texte qui joue ici (Vous comprendrez mieux par après).

Je vais vous donner 2-3 raccourcis d'Olly supplémentaires assez intéressants:

Si vous voulez voir où saute un saut, voir ce qu'il y a dans un CALL,... (je ne parle pas ici d'exécution, je parle lorsque vous lisez le code), cliquez sur la ligne du saut, du call ou autre et appuyez sur ENTER.

Si vous voulez revenir en arrière (après avoir poussé sur ENTER par exemple, appuyez sur '-').

Regardons ce qui suit :

On pousse 4 choses dans la pile :

1) 6 (correspond justement à la longueur de la chaîne qui suit ("Serial") )

2) 403000 (Olly comprend que ce n'est pas un nombre mais bien un pointeur vers la chaîne de caractères "Serial" qui se trouve en 403000)

3) eax est la valeur retournée par la fonction GetDlgItemText qui se trouve juste au dessus. Ça correspond à la longueur de la chaîne de caractère qui se trouvait dans la zone de texte.

4) 403020 est justement l'adresse du buffer dans lequel on a placé le texte de la zone de texte (Jetter un oeil à la fonction GetDlgItemText en 004010B7 pour vérifier)

Après le CALL, on teste si eax vaut 0 (OR eax,eax).

S'il vaut 0, on ne saute pas, donc EnableWindow sera à FALSE et donc notre bouton (Olly l'indique clairement) ne sera pas disponible.

Donc pour pouvoir activer le bouton il faut que eax soit différent de 0 après le CALL 00401000.

Voyons ce que le call 00401000 fait. Appuyez sur F8 jusqu'à arriver à la ligne du CALL 401000. Sur cette ligne appuyez sur F7 pour rentrer dans le CALL.

Voici la fonction :

J'ai commenté sans regarder le reste, si vous regardez l'ordre de ce qui a été poussé dans la pile, vous retrouverez facilement à quoi correspondent le premier paramètre, le 2eme,...

Voici donc ce que fait la fonction : On copie le pointeur de tête de pile dans EBP (EBP pointe donc sur le sommet de la pile) ceci afin de pouvoir accéder facilement aux paramètres passés à la fonction.

On pousse ensuite dans la pile les registres qui seront modifiés par la fonction sauf eax vu qu'il doit contenir une valeur de retour (lorsque l'on crée une fonction c'est mieux que les valeurs restent identiques avant et après l'appel de la fonction, c'est pour cela qu'on sauve les registres que l'on modifie).

On met ensuite eax à 0.

Après on compare l'avant-dernier paramètre (si vous regardez ce qui est placé dans la pile avant l'appel de la fonction vous verrez que cet avant-dernier paramètre est la longueur du serial entré) et le premier (la longueur du bon serial), si ces longueurs sont différentes on saute et eax vaudra 0.

Ensuite on place dans EDI le dernier paramètre (l'adresse du buffer), dans ESI le 2eme paramètre (l'adresse du bon serial) et dans ECX la longueur du serial.

On décrémente ECX d'une unité. En effet le premier caractère commence en 0, donc si la longueur est 2 par exemple je dois commencer en 1 et non en 2 (donc longueur -1)

La fonction qui suit (REPE instruction) répète instruction jusqu'à ce que ZF = 0 ou CX=0. A chaque itération (exécution) CX est décrémenté.

CMPS sert à comparer byte à byte 2 caractères. Elle positionne le ZF à 1 si les caractères comparés sont égaux et à 0 s'ils ne le sont pas.

Donc REPE CMPS compare 2 chaînes jusqu'a ce qu'un caractère soit différent ou qu'on soit arrivé à la fin de la chaîne (val dans ECX).

Ensuite si le ZF est à 0 (donc il y a eu un caractère différent), on saute et EAX ne sera pas mis à 1, sinon on ne saute pas et on force EAX à 1.

Pour finir, on dépile et on quitte la fonction.

On vient donc de voir que le serial doit être égal à .... "Serial" ! ;)

Bon crack,

Crisanar

Dernière mise à jour le 26/03/2016 16:24


Cours n°4 - Patching & Reverse Engineering

Ce dont vous avez besoin : Olly, ResHacker, CAscii et le même crackme qu' au cours précédent : FishMe1

Comme l'analyse du logiciel, l'observation et l'analyse du code ont déjà été faites au cours précédent, pas besoin des les refaire ici.

Patching

On va enfin s'amuser :D.

¤ Utiliser ResHacker

Imaginons qu'on n'ait pas pris le temps de faire toute l'analyse que l'on vient de faire dans le cours précédent (il y a des gens paresseux ;) ), dans ce cas, prenons ResHacker. Grâce à ce logiciel, nous allons pouvoir modifier les ressources de notre exécutable.

Faites File>Open et ouvrez le crackme ou faites un glisser déposer.

Il n'y a qu'une ressource dans l'arborescence Ouvrez l'arborescence jusqu'au bout et cliquez sur 1033. Une fenêtre s'ouvre en affichant notre fenêtre du crackme. Faites un clic droit sur le bouton "Vérifier !">Edit control et chercher dans la nouvelle fenêtre la valeur WS_DISABLED qui doit être en jaune.

Cliquez dessus pour l'afficher en gris, puis OK.

Ensuite appuyez sur Compile Script.

Maintenant fermez ResHacker et enregistrez lorsqu'il vous le propose (n'oubliez pas l'extension ".exe" si vous n'enregistrez pas sur l'ancien exécutable).

Si vous lancez l'exe, vous pourrez maintenant cliquer sur "Vérifier !", mais il suffit de changer la valeur dans la zone de texte pour que ça ne fonctionne plus!

¤ Patcher bêtement

Les façons de faire sont quasi infinies. Voici un exemple (en rouge ce qui a été patché) en modifiant un saut afin que l'on saute toujours vers la routine qui active le bouton:

Voici un autre exemple où j'ai modifié la routine qui désactive le bouton afin qu'elle l'active:

Reverse Engineering

Je définirais le Reverse Engineering comme ceci : "C'est l'action de modifier un programme afin qu'il fasse une chose pour laquelle il n'avait pas été programmé au départ".

Je ne sais pas si c'est réellement du RE que nous faisons ici, mais nous allons modifier le programme afin qu'il écrive dans la zone de texte le bon serial lorsque l'on s'est trompé.

Ce qu'il nous faut pour cela, c'est une fonction qui écrit du texte dans un contrôle. Nous en avons une : SetDlgItemTextA qui sert à définir le caption du label.

Cette API demande 3 paramètres : l'adresse du texte (on l'a), l'ID du controle (on va le trouver) et l'hWnd (on l'a aussi).

Pour trouver l'ID du controle (la zone de texte), reprenons ResHacker. Ouvrez l'exe et cliquez sur la zone de texte. Une petite '*' rouge doit se trouver devant la ligne correspondant à celle-ci.

Le numéro qui suit le CONTROL "" est l'ID de la zone de texte, c'est-à-dire 201. Mais celui-ci est en décimal, alors que dans Olly il est en hexa.

Lancez CAscii, dans Code Ascii(=decimal) tapez 201 et appuyez sur ENTER. La valeur en hexa correspondante est donc 0C9h (on aurait pu le deviner en regardant la fonction GetDlgITemText).

Nous avons tout ce qu'il nous faut. Maintenant il faut trouver de la place pour mettre cela.

Logiquement on veut que ce code soit exécuté lorsque l'on a tapé un mauvais serial, on peut donc le mettre à la place du EnableWindow, ... FALSE. Mais nous n'aurons pas assez de place. On va donc regarder dans les bytes libres à la fin de l'exe, là-bas on aura largement la place.

Placer vous sur une ligne libre.

La première chose à faire est de pousser les paramètres :

1) L'adresse du texte : tapez PUSH 403000 (C'est l'adresse du bon serial qu'on avait trouvée tantôt)

2) L'ID du control : tapez PUSH 0C9 (LE 0 devant est obligatoire)

3) hWnd on le retrouve un peu partout, il est enregistré en [EBP+8] : tapez PUSH [EBP+8]

Ensuite L'appel à la fonction :

4) L'appel à la fonction : tapez CALL SetDlgItemTextA (respectez la casse)

Et enfin le retour :

5) Après avoir exécuté ceci il faut retourner quelque part, nous voyons qu'après le CALL EnableWindow,...,FALSE on sautait en 4010EE, puisqu'on ne doit plus exécuter cette fonction (EnableWindow,...,FALSE) autant sauter directement en 4010EE: tapez JMP 4010EE.

Après ces manipulations vous devez avoir ceci (la ligne à laquelle vous avez commencé à taper peut être différente) :

C'est presque fini. Notre code est défini mais rien ne l'appelle encore! A la place du premier paramètre du CALL EnableWindow,...,FALSE (PUSH 1 en 004010D2) tapez JMP 0040116C (ou une autre adresse si vous avez placé votre code un peu plus bas ou un peu plus haut que moi).

Et voilà, maintenant la zone de texte affichera toujours le bon serial! :D

Mais cette méthode ne me convient pas entièrement. La zone de texte est "bloquée", même si le bon message est toujours affiché dedans.

Voici un autre exemple ou le Reverse Engineering prend encore plus de sens.

Nous allons ajouter un bouton qui mettra le bon serial dans la zone de texte lorsqu'on on cliquera dessus.

Prenez ResHacker, ouvrez le crackme, faite un clic droit sur la fenêtre représentant l'apparence du crackme et sélectionnez "Insert control". Dans la fenêtre qui s'est ouverte, cliquez sur l'icône représentant un bouton. Dans la zone de texte "Caption" entrez le texte que vous désirez voir apparaître sur votre nouveau bouton. Dans la zone "ID", entrez un numéro encore non utilisé, 203 par exemple. Lorsque c'est fait, cliquez sur "OK". Redimensionnez ensuite votre bouton et placez le à votre convenance. Cliquez sur "Compile Script", fermez ResHack et enregistrez.

Vous venez d'ajouter un bouton mais si vous exécutez votre nouveau crackme, lorsque vous cliquez dessus il ne se passe rien! Normal, on ne lui a pas encore associé de code.

Prenez la source que je vous avais fournie déjà au cours précédent et regardez un peu la structure de test d'appui sur un bouton :

.ELSEIF uMsg == WM_COMMAND
mov eax, wParam
mov edx, wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax == IDC_BTNOK ;Si on a cliqué sur le bouton OK
INVOKE GetDlgItemText,hWnd,IDC_EDT1,ADDR strBuffer,cTAILLEBUFF
INVOKE MessageBox, hWnd, ADDR strBuffer,ADDR strTitle,MB_OK
.ENDIF
.ENDIF
.ELSEIF uMsg == WM_CLOSE

Ce qu'il faut c'est rajouter un test dans BN_CLICKED pour ax==203 (l'ID de notre bouton). Mais comment savoir ou le mettre?

Voici comment elle est traduite par le compilateur (il suffit de repérer les 2 fonctions GetDlgItemText et MessageBox pour trouver) :

On peut donc déduire que la constante WM_COMMAND est traduite par 111, BN_CLICKED est traduit par 0 ( OR DX,DX = test si égal à 0) et IDC_BTNOK par 0CA (on aurait pu savoir celui-ci en regardant avec ResHacker l'ID du bouton "OK")

Ouvrez avec Olly le crackme modifié. On va rechercher cette structure dans notre crackme. On sait déjà que cette procédure doit se trouver dans la procédure associée à la dialogbox, donc pas besoin de chercher ailleurs.

On retrouve les 3 nombres dans le même ordre (entourés). Je ne sais pas si les flèches vous aiderons mais bon ;)

Voilà donc les étapes avant d'arriver au bouton : on teste si WM_COMMAND (111h) si oui, on retravaille wParam ( [EBP+10h] ), on teste si dx==BN_CLICKED (0) et enfin on teste si ax==BOUTONOK (0CAh), si ax!=BOUTONOK on saute en 0040110B, si ax==BOUTONOK, lorsqu'on a éxécuté la routine associée au bouton, on saute en 0040110B également.

Nous devons donc sauter en 0040110B après avoir éxécuté la routine de notre bouton. Mais où placer le test de notre bouton? Simplement en dessous de CMP AX,0CAh, à la place de sauter vers 0040110B, on va sauter vers notre bouton.

Pour l'écriture de notre bouton 2 solutions sont possibles, soit on teste si l'ID correspond bien à notre bouton, soit on se dit que comme il n'y a que 2 boutons, si le code envoyé n'est pas celui de BOUTONOK, ça ne peut-être que celui que l'on vient de rajouter.

Par soucis de "propreté", j'écrirai un test de l'ID de notre bouton avant d'exécuter la routine qui copiera le bon serial, on n'est jamais trop prudent.

Comme la dernière fois, cherchons de la place à la fin de l'exe.

Placez vous sur une ligne vide et voici ce que vous devez faire :

¤ Test de l'ID

Prenez CAscii, tapez l'ID de notre nouveau bouton (203) dans Code Ascii(=décimal), appuyez sur Entrer. L'ID en hexa de notre bouton est donc CBh. En assembleur, une valeur ne peut pas commencer par une lettre, il faut donc placer un '0' devant ce qui donne 0CBh.

1) Il faut donc tester si l'ID qui se trouve dans AX est bien 0CBh : tapez CMP AX,0CBh

2) Si ce n'est pas égal il faut sauter vers la suite, donc 0040110B (rappelez-vous c'était là que l'on sautait si AX n'etait pas égal à l'ID du bouton OK) : tapez JNE 0040110B

¤ Copier le bon serial dans la zone de texte

C'est exactement le même code qu'ici au dessus :

PUSH 403000

PUSH 0C9

PUSH [EBP+8]

CALL SetDlgItemTextA

¤ Retourner à la suite

Il faut retourner à la suite, c'est donc encore la même adresse à savoir 0040110B : tapez JMP 0040110B

Il reste encore une petite chose avant de terminer, tout est défini mais on n'arrive jamais à cette routine vu que l'on a pas encore changé le CMP AX,ID_BOUTONOK

JNZ 0040110B en JNZ 0040116C (l'adresse du début de notre nouvelle routine de test que l'on vient d'écrire, ca peut différer selon la ligne où vous avez commencé)

Allez à la ligne du JNZ 0040110B (0040107C) et tapez JNZ 0040116C.

Testez! Si ça fonctionne bien, faites un clic droit>Copy to executable>all modifications fermez la fenêtre qui s'ouvre et enregistrez.

Bon crack,

Crisanar

Dernière mise à jour le 26/03/2016 16:25


Cours n°5 - Olly fait des siennes

Ce dont vous avez besoin : Olly, PEID, LordPE et le crackme : KeygenMe1

Analyse du logiciel

PEID trouve enfin que le programme a été écrit avec MASM.

Observation

Deux zones de textes, une pour notre nom, une pour notre serial. Si on entre rien => rien! Si on entre un nom et un serial bidon => "Sérial invalide".

Analyse du code

En regardant un peu le code, on voit rapidement une API intéressante : GetDlgITemText qui renvient deux fois (tiens, on a justement 2 zones de texte).

Juste en dessous, il y a "Serial invalide", "Vous avez réussi" et "Bravo" suivis d'une MessageBox. Il y a de grandes chances que notre routine de serial soit là-bas!

On va donc poser un BP en 4010E3 (CMP eax,3). On lance le programme (F9), on entre notre nom et un serial> "Vérifier !"> ça breake.

En passant les APIs GetDlgItemText avec F8, nous voyons que l'on récupère d'abord notre nom puis le serial.

Regardez un peu le code et réfléchissez :

Commençons du bas, avec la message box en 40115E.

Les adresses de Text et Title sont respectivement enregistrées en 403147 et 403143. Juste au dessus, en fonction de eax (en vert), on va mettre dans ces adresses soit un pointeur vers le bon message soit vers le mauvais. Le CALL juste au dessus du OR EAX,EAX (en 40111A) est très certainement un test de validité du serial entré!

Pour plus de lisibilité, prenez l'habitude d'ajouter des labels sur les CALLs. Cliquez sur la ligne du CALL pour la sélectionner puis faites ENTER. Vous vous retrouvez sur la première ligne de la fonction. Là, appuyez sur ' : ' pour entrer un label (c'est une description, ça ne change rien au code). Tapez un nom du style "TestSiBonSerial".

On regardant le paramètre Buffer de la 2eme GetDlgItemText, en rouge, nous voyons que le serial entré est enregistré en 403044 (passez avec F8 au dessus). C'est justement un des 2 paramètres de la fonction présumée de test du serial. Le 2eme paramètre(403028, en bleu) est celui qui est utilisé par la fonction en 4010F2 et dont l'autre paramètre est le nom entré.

Entrez également un label pour cette fonction (401021 qui est appelée en 4010F2). Par exemple "GenSerial" (vous verrez ci-dessous pourquoi)

Résumons : Nous avons une fonction juste après la récupération du nom, elle prend deux paramètres : l'adresse où est enregistré notre nom et une autre (en bleu). Ce deuxième paramètre(en bleu) est utilisé juste après la récupération du serial entré par une fonction qui retourne une valeur dans EAX qui permet de savoir si le serial est valide ou pas.

Il se pourrait bien que ce 2eme paramètre soit le serial qui correspond à notre nom.

Pour vérifier celà, on va avancer en pas à pas(F8) jusqu'à la ligne 401110, lorsqu'on y est, on recopie ce qui se trouve en 403028 (arrêtez vous au 0 de fin de chaîne) (Pour recopier, regardez dans la fenêtre DUMP en bas à gauche, faites un clic droit>Goto>Expression>403028). Pour "Crisanar", le serial que je trouve est : c1x+-c"--6-l"c1

On fait un petit essai : on vire les BP, F9, on entre le même nom et le serial qu'on vient de trouver et ... ça marche! La message box affiche le bon message!

Bizarre quand même, c'était plus facile qu'aux cours précédents!

Ben oui, vous vous en doutez, ce n'est pas fini! Lancez le programme normalement (pas dans Olly quoi) rentrez votre nom et votre serial => "Serial invalide". Zut! Mais si vous le relancez dans Olly, le serial est toujours valide! Ça ne vous rappelle rien?

Regardez un peu les APIs utilisées (les JUMPs DWORD en fin de programme), on y retrouve une à qui on a déjà eu à faire "IsDebuggerPresent".

Mais on a beau fouiller dans le code, on ne la voit nulle part!

L'appel à cet API (le JUMP DWORD tout à la fin du programme en fait) se fait en 40116C. En fait j'ai utilisé une petite astuce pour cacher l'appel de cette fonction : je n'ai pas fait un CALL IsDebuggerPresent directement mais un CALL [addresse_de_la_fonction]. Olly ne reconnaît plus la fonction de cette façon. Pour trouver où l'appel se fait, soit on cherche ce nombre dans le code (un truc du style [0040116C] ), soit on sélectionne la ligne du JUMP DWORD de la fonction, on ajoute un label (' : ') et on ecrit le nom de la fonction comme label. Quelque soit la méthode que vous choisirez, vous devriez trouver IsDebuggerPresent en 40106A.

Vous voyez qu'il n'y a pas de CALL. En fait c'est un CALL détourné. Pour comprendre ce qui se passe ici, il faut savoir ce que fait un CALL: Il place dans la pile l'adresse où il doit retourner après l'exécution du CALL et ensuite il saute à l'adresse indiquée par le CALL.

Un RET prend l'adresse qui se trouve au sommet de la pile et saute à cette adresse.

Voici le bout de code commenté :

On pousse l'adresse de retour qui est la ligne en dessous du RET puis on pousse l'adresse qui se trouve en 403039 (l'adresse de IsDebuggerPresent que l'on voit grâce au label que l'on a ajouté) et le RET appelle IsDebuggerPresent.

Ces 3 lignes équivalent à un CALL IsDebuggerPresent mais cette façon d'écrire le CALL dupe Olly et "cache" à l'utilisateur l'appel à la fonction.

Si vous vous rappelez, cette fonction retourne 1 dans EAX si le programme tourne sous un environnement de débuggeur et 0 dans le cas contraire.

Or il y a un XOR AL,1.

A | B | XOR
0 | 1 | 1
1 | 1 | 0

Ce XOR inverse donc la valeur de AL. Si AL vaut 1, il devient 0 et inversement.

On soustrait ensuite à la variable en 40103D la valeur de AL.

Rien ne vous étonne dans cette valeur? Et oui, c'est du code et non une variable (les variables commencent ici en 403000). Allons regarder ce qui se trouve en 40103D : CMP ECX,4. On va donc retirer ou non 1 à 4. Et ce CMP, où se trouve-t-il? Dans une fonction qui commence en 401021. Justement dans la fonction que nous avions vu tantôt et à laquelle nous avons ajouté un label : "GenSerial"!

Voici donc la solution à notre problème : lorsque nous sommes dans un environnement de débuggeur, la fonction qui génère le serial est différente.

Création du keygen

Je considère que vous êtes assez grand pour analyser la routine de création du Serial tout seul et je vais faire le paresseux, c'est à dire que l'on va créer un keygen à partir du programme lui-même.

Une méthode assez courante est d'afficher une MessageBox avec le bon serial. Cette méthode est assez simple du fait que l'API MessageBox est souvent incluse dans la plupart des programmes mais présente le gros inconvénient de ne pas pouvoir recopier le serial affiché directement.

Il faudrait donc pouvoir faire comme dans le cours 4, c'est-à-dire afficher le bon serial directement dans la zone de texte, mais pour cela il nous faut une API (SetDlgItemText) que nous n'avons pas ici (SendMessage,...,WM_SETTEXT devrait fonctionner aussi mais le problème reste le même vu que nous n'avons pas de SendMessage non plus).

Pour ajouter une API, quitter KeygenMe1(même dans Olly) lancez LordPE, cliquez sur PE Editor>Choisissez le fichier>Directories>Le bouton "..." de la ligne ImportTable. Dans la fenetre qui s'est ouverte, faites un clic droit sur une des DLL affichée>add import...

En regardant dans le WIN32SDK à SetDlgItemText sur Quick info, nous voyons que cette fonction se trouve dans user32.dll.

Tapez donc user32.dll dans la zone pour la dll et et SetDlgItemTextA dans la zone pour le nom de l'API (attention, sans le 'A' à la fin, vous risquez d'avoir des problèmes). Appuyez ensuite sur le bouton ' + ' > OK. Cliquez sur la dll que vous venez d'ajouter et dans la ligne de la nouvelle API, retenez le nombre sous ThunkRVA(moi j'ai 501D). Fermez la fenêtre > Save > OK > Save > OK et fermez LordPE.

L'API maintenant rajoutée il nous suffit de l'utiliser. Relancez Olly et ré-ouvrez KeygenMe1.

Sachez que vous ne pouvez pas utiliser directement l'API que vous venez d'ajouter en l'appelant par son nom, Olly ne posera pas de problème pour le faire mais votre exécutable ne sera plus portable. Si vous l'exécutez sur une autre machine, il plantera.

Pour pouvoir appeler correctement votre API, voici comment faire : vous ajoutez l'ImageBase(habituellement 40000) à la valeur que je vous ai dit de retenir ce qui donne 40501D. Attention ceci ne fonctionne que si vous avez rajouté une API. Pour les APIs déjà existantes il faut faire ImageBase + FirstThunk + (ThunkRVA - FirstThunk). Pour une API ajoutée avec LordPE, FirstThunk = ThunkRVA donc c'est moins compliqué.

Vu qu'ici nous ne comptons pas utiliser l'API plus d'une fois il nous suffira de faire un CALL [40501D] pour appeler notre API, sinon il est plus "propre" d'ajouter un JMP DWORD sous les autres JMP DWORD des APIs(à la fin du programme) et d'appeler ce JMP plutôt que l'API directement. Si on avait fait comme cela, pour plus de facilité placez un label sur le JMP DWORD de la nouvelle API et lorsque vous voudrez l'utiliser, faite un CALL Votre_Label. Ici nous choisirons la simplicité et nous nous contenterons d'un simple CALL.

Avant d'écrire notre nouvelle fonction, regardons tout d'abord quand nous voulons exécuter notre fonction. Pour moi après l'affichage de la message box disant que le serial est invalide est un bon endroit.

Il faudra écrire notre fonction quelque part. Il y a de la place en 4011CC, nous pouvons le faire là-bas. Pour arriver à cet endroit, il va falloir écrire un JMP 4011CC. Si nous l'écrivons en 401123, nous allons écraser les données qui s'y trouvent. Il faut donc les recopier quelque part. Une fois que cela est fait, écrivez donc JMP 4011CC en 401123.

En 4011CC, recopiez donc le MOV DWORD PTR [403147],403000 que vous venez d'écraser.

Ensuite il faut utiliser l'API SetDlgItemText que l'on vient d'ajouter.

Les paramètres à passer à cette fonction sont :

1) Le hWnd de la fenêtre : il se trouve comme d'habitude en [EBP+8], si vous n'êtes pas sûr, vérifiez lors de l'appel du fonction quelconque dans le programme.

2) L'ID de la zone de texte : on le connaît grâce à la 2eme GetDlgItemText, c'est 68h (Vous pouvez vérifier avec ResHack)

3) L'adresse de la chaîne de texte à écrire : lors de notre analyse tantôt, nous avions remarqué que le bon serial était enregistré en 403028.

Plaçons nous à la fin du programme, là où il y a de la place (en 4011CC par exemple) et écrivons tout cela.

Rappelez-vous qu'il faut pousser les paramètres dans l'ordre inverse :

PUSH 403028 ; L'adresse de la chaîne de caractère

PUSH 68h ; ID de la zone de texte

PUSH DWORD PTR [EBP+8] ; hWnd de la fenêtre

CALL [40501D] ; SetDlgItemTextA

Après avoir défini le bon texte, nous allons retourner comme si rien ne s'était passé en 40112D, au 2eme MOV => JMP 40112D.

En faisant F9, on peut constater que ça fonctionne. On entre notre nom, ça affiche serial invalide puis ça entre le bon serial. Le problème c'est que ce serial est masqué par des *.

Sauver les modifications et fermez le programme.

Pour enlever les étoiles, prenez ResHack et ouvrez KeygenMe1 (la version que vous venez d'enregistrer).

Dialog>100>1033, une fenêtre s'ouvre cliquez sur la zone de texte où l'on entre le serial. Une petite * s'affiche sur la ligne de code correspondant à la zone de texte.

Vous remarquerez le "| ES_PASSWORD" qui défini que la zone de texte doit s'afficher avec des étoiles. Supprimer donc cela>Compile Script , File>Save.

Voilà, vous avez un beau keygen ;)

Pour vous exercer, essayez de rajouter un label avec votre pseudo, de changer le texte du bouton en "Générer !", le titre de la fenêtre en "Keygen" et de ne plus afficher de message box mais directement le bon serial.

Voici un exemple :

Bon crack,

Crisanar

Dernière mise à jour le 26/03/2016 16:25


Cours n°6 - Introduction aux packers

Ce dont vous avez besoin : Olly, PEID, LordPE, ImpREC et le crackme : UnPackMe1

Avant de commencer l'analyse du logiciel, je vais tenter de vous expliquer brièvement ce que sont les packers.

Ce sont des logiciels qui permettent de modifier un exécutable donné afin de le compresser et/ou le protéger contre le débugguage, le dump,... Ce n'est en fait qu'une protection rajoutée sur le programme originel. En gros, le principe est une sorte d'encapsulation: le programme en mémoire est exactement pareil qu'auparavant, il n'y a que la routine de "décryptage" (le loader) qui est ajouté au démarrage. Ce qui veut dire que dans la plupart des cas (il n'y a pas de standard en la matière), une fois que l'on a trouvé l'OEP (original entry point, le point d'entée du programme avant qu'il ne soit packé), le programme est tel qu'à son origine.

C'est là qu'arrive le dump. Cela consiste à faire une copie du programme en mémoire dans un fichier.

Mais le fichier ainsi obtenu n'est toujours pas fonctionnel, pour pouvoir le rendre fonctionnel, il faut reconstruire l'IAT. Sans rentrer dans les détails (ma connaissance n'est pas assez poussée et de toute façon ce n'est pas le but ici), l'IAT comporte 2 tableaux dans lesquels sont repris les dll utilisées ainsi que les adresses en mémoire des fonctions (APIs) quelles proposent. L' IAT de l'exécutable sur le disque n'est pas le même qu'en mémoire. Sur le disque, un des 2 tableaux contient le nom des fonctions, lorsque le loader du système d'exploitation charge le programme en mémoire, il remplace ces noms par les adresses en mémoire des fonctions correspondantes. Donc l'IAT recopié tel quel de la mémoire n'est plus valide. Il faut donc le reconstruire.

Mais ne vous tracassez pas de tout ce bla bla, c'est beaucoup plus simple que ça en a l'air ;).

Je ne vais pas ici analyser le packer, je vais seulement décrire les étapes pour arriver à obtenir un programme décompressé et fonctionnel à partir d'un programme compressé avec UPX.

Analyse du logiciel

PEID nous indique "UPX 0.89.6 - 1.02 / 1.05 - 1.24 -> Markus & Laszlo". UPX n'est pas, à proprement parler, une réelle protection, c'est un packer qui permet de compresser notre programme. Il est toutefois très simple à unpacker et convient donc bien pour une introduction aux packers.

Observation

Vous remarquerez qu'en exécutant le logiciel normalement vous ne voyez aucune différence par rapport à un programme "normal".

Le but de ce cours n'étant pas de cracker ce logiciel, on ne se tracassera pas d'observer ce que fait le programme.

Analyse du code

La plupart des packers (comme des compilateurs d'ailleurs), ont une signature, c'est à dire une suite d'instructions/octets, une particularité,... qui permet de les distinguer.

Celle d'UPX se caractérise par des sections se nommant UPX1, UPX2,... et un pushad, popad de part et d'autre du loader (la routine qui va "décrypter/décompresser" le programme) :

Recherche de l'OEP

Le packer ne doit rien modifier, le programme original doit retrouver les registres dans l'état tel qu'ils étaient au démarrage du loader. C'est à ca que sert pushad et popad. PUSHAD pousse tous les registres dans la pile et POPAD les restaure tous.

Après avoir décrypté/décompressé, le loader doit donner la main au programme originel pour qu'il s'exécute comme il l'aurait fait auparavant.

Ici c'est le JMP 401120 (retenez cette valeur) juste en dessous du POPAD qui fait cela.

La méthode générale pour trouver l'OEP d'un programme (avec des protections de bas niveau) est de regarder l'EOP d'un programme non protégé puis de protéger ce même programme par le packer que l'on souhaite analyser. Ensuite, on exécute le programme en pas à pas et on regarde quand on arrive à l'OEP qu'on avait retenu les étapes que l'on vient de passer. L'OEP revient souvent après les mêmes instructions pour un certain packer et il suffit de retrouver ces instructions dans un autre programme protégé par le même packer pour arriver à trouver l'OEP de cet autre programme.

Pour UPX c'est très simple, c'est :

POPAD

JMP OEP

DUMP

Maintenant que l'on a trouvé notre OEP, il faut dumper le programme décompressé en mémoire dans un fichier.

Je vous rappelle que le packer doit être invisible pour l'exécutable, donc une fois arrivé à l'OEP, le programme est sensé être comme il l'était avant d'être compressé.

Nous allons donc poser un BP sur le JMP OEP (en 41769F dans notre exemple). Une fois ceci fait, appuyez sur F9. Lorsqu' Olly breakera, le programme sera décompressé.

Laissez bien le programme en pause et prenez LordPE.

Dans la liste des processus actifs, cherchez l'UnPackMe. Une fois que vous l'aurez trouvé, faites un clic droit dessus et sélectionnez "Dump full...", enregistrez maintenant votre programme dumpé.

Si vous tentez de l'exécuter, vous aurez un beau message d'erreur. Et oui, l'IAT dont je vous ai parlé tantôt n'est toujours pas reconstruite.

Avant de lancer ImpRec pour reconstruire l'IAT, on va changer l'EP de notre exécutable dumpé. En effet, l'EP pointe sur le début du loader mais vu que notre programme est décompressé, nous n'avons plus besoin du loader.

Dans LordPE, cliquez sur "PE Editor" et sélectionnez le fichier dumpé. Dans la case "Entry point", remplacez la valeur existante

par celle que je vous ai di de retenir ici plus haut (l'OEP, à savoir 401120 - ImageBase => 1120). Cliquez ensuite sur Save, puis OK et fermez LordPE.

Reconstruction de l'IAT

Attaquons-nous maintenant à la reconstruction de l'IAT.

Pour cela, prenez ImpREC. Si vous n'avez pas fermé l'UnPackMe depuis tantôt il n'y a pas de problèmes, sinon relancez-le. Attention, il doit être décompressé sinon ça ne fonctionne pas.

1) Dans la liste des processus actifs, sélectionnez l'UnPackMe.

2) Changez la valeur de l'Entry Point par l'OEP (1120)

3) Cliquez sur "IAT autosearch", logiquement une messagebox s'affiche pour vous indiquer quelle a trouvé quelque chose.

4) Cliquez sur Get Imports. La liste va se remplir avec les APIs utilisées par le programme.

5) Cliquez sur "Fix Dump", sélectionnez le fichier dumpé et logiquement si vous ne vous êtes pas trompé et que j'ai bien expliqué, vous avez un bel exécutable "PackerFree". L'exécutable n'est pas à 100% égal au fichier original mais le code est exactement pareil.

L'avantage de dumper un fichier est que lorsque vous le chargez dans Olly, vous voyez directement le code de l'exécutable et non le loader et , de plus, si vous patcher le fichier, vous pourrez enregistrer les modifications sans problèmes.

Seulement, si vous essayez de charger votre exécutable dumpé dans Olly, il va se lancer sans breaker sur l'EP. Ceci est du au fait que les sections ne sont plus alignées.

Pour corriger ce problème, relancez LordPE, cliquez sur PE Editor et sélectionnez votre exécutable dumpé.

Cliquez sur "sections" et regardez par rapport au VOffset dans quelle section doit se trouver l'EP.

L'EP se trouve donc dans la section UPX0 (qui correspondait probablement au .text avant d'être compressée par UPX) puisque sa valeur est 1120 et que la section UPX0 va de [1000 à F000[

Fermez cette fenêtre et dans le fenêtre précédente, remplacez la valeur de BaseOfCode par le VOffset du début de la section dans laquelle se trouve l'EP, donc 1000.

Cliquez sur Save puis quittez LordPE.

Maintenant votre exécutable est pleinement fonctionnel.

Pour vous entraîner, vous pouvez vous amuser à essayer de trouver un serial valide pour votre nom ;). Envoyez-moi vos réponses.

Bon crack,

Crisanar

Dernière mise à jour le 26/03/2016 16:27


D:\_Essais II\Sommaire détaillé.rtf

Sommaire détaillé des cours

o Cours n°0 : Pour débuter
Explication du fonctionnement de deux logiciels indispensables pour cracker :
WinDasm et l’éditeur hexadécimal.

o Cours n°1 : L'hexadécimal et les sauts conditionnels
Comprendre ce qu’est le langage hexadécimal et comment il fonctionne. Et
explication des sauts et schéma d’une protection basique pour comprendre
ce qu’il faut faire pour cracker un programme.

o Cours n°2 : Cracker un crack-me
Mise en application de la théorie en crackant un petit crackme en modifiant
un saut.

o Cours n°3 : Cracker un crack-me
Mise en pratique du 2ème cours en crackant un crackme qui utilise un saut
conditionnel pour vérifier le serial.

o Cours n°4 : Trouver le bon serial
Utilisation d’une méthode avec le débuggeur de WinDasm pour trouver un
serial.

o Cours n°5 : A mort le crack-me !
Cracker et trouver le bon serial d’un crackme, puis créer un keygen après
avoir trouvé comment est généré le bon serial en fonction du nom.

o Cours n°6 : WinRAR 2.71
Cracker WinRar en enlevant un nag-screen et effaçant les informations qui
nous rappellent que nous utilisons une version d’évaluation. Puis création du
crack pour pouvoir le diffuser.

o Cours n°7 : Cracker un crack-me
Un cours simple pour cracker un crackme, rien d’original dans ce dernier.

o Cours n°8 : Teleport Pro 1.29.1820
Cracker le logiciel Teleport Pro qui comporte quelques protections comme la
vérification du nom du .exe et qui nous empêche de modifier ce programme.
Et essayer de s’enregistrer en plus d’empêcher l’expiration du logiciel.

o Cours n°9 : Cracker un prog compressé
Cracker et trouver le bon serial d’un crackme qui n’a pas de Strings Data
Reference étant compressé.

o Cours n°10 : Cracker un crack-me et trouver le serial
Trouver le serial en utilisant le débuggeur et en analysant les lignes de code
en assembleur. Et faire en sorte que le programme affiche le bon message.

o Cours n°11 : Le crackme qui se crack lui-même
Après avoir trouvé ce qu’il fallait modifier pour cracker le crackme, on va
rajouter des lignes d’instructions en assembleur dans le programme pour
qu’il se patche lui-même. Puis on lui fera afficher le serial à la place du
message d’erreur.

o Cours n°12 : Reverse Engineering sur la calculatrice Windows
Pas vraiment du cracking mais analyse intéressante du programme. On va
ajouter un menu dans la calculatrice Windows pour qu’il affiche une msgbox
à l’aide d’un éditeur de ressource et d’instructions en assembleur qu’on
rajoutera.

o Cours n°13 : Donne le serial au monsieur
Rajouter un bouton au crackme pour qu’il affiche le bon serial quand on
clique dessus.

o Cours n°14 : Nag d'un crackme en Visual Basic
Comprendre le fonctionnement des fenêtres en Visual Basic pour pouvoir
enlever un nag-screen en se servant uniquement d’un éditeur hexadécimal.

o Cours n°15 : Crackme avec Keyfile
Permet d’apprendre à utiliser OllyDbg à la place de WinDasm. Deux
méthodes pour cracker ce crackme : une méthode normale en modifiant des
sauts et une méthode où il va falloir comprendre ce qu’il faut faire pour
rendre notre keyfile valide en affichant notre nom.

o Cours spécial : L’Assembleur
Les bases du langage de programmation le plus utile pour les crackeurs. La
connaissance de ces bases permet de mieux analyser un programme par la
suite. Explication des registres, de la pile, des différents sauts, opérations, et
autres instructions...


D:\_Essais II\Outils nécessaires.rtf

Outils nécessaires

Pour cracker il faut des programmes spécifiques, voici les catégories des programmes
qu’on peut utiliser plus ou moins souvent en cracking avec des exemples de programmes
que vous pourrez être ammené à rencontrer :

- Désassembleur :
Description : il permet d’afficher le code source du programme en assembleur qui
nous permet en l’étudiant de voir ce qu’il faut cracker et à quel endroit. Un outil quasi
indispensable.
Exemples : WinDasm, IDA, …

- Débuggeur :
Description : il permet d’analyser un programme lors de son exécution et de voir
« en direct » la valeur des registres et des opérations que le programme effectue.
Peut être utile pour trouver un serial.
Exemples : OllyDbg, SoftIce, …

- Editeur hexadécimal :
Description : l’éditeur affiche le contenu du programme en hexadécimal et ascii. Il
est utilisé pour faire des modifications dans un programme pour le cracker.
Exemples : HexDecCharEditor, Hex WorkShop, WinHex, Hiew, …

- Analyseur :
Description : afin de connaître le langage de programmation dans lequel a été
créé le programme ou le logiciel avec lequel il a été compressé ou crypté.
Exemples : StudPe, Peid, …

- Patcheur :
Description : si vous souhaitez créer un patch pour cracker un programme, vous
aurez donc besoin d’un patcheur. Il va comparer le fichier original et le cracké et à
partir de là il va créer le patch.
Exemples : Graphical-PatchMaker, CodeFusion, WinPatchEngine, …

- Unpackeur :
Description : il permet de décompresser ou décrypter automatiquement les
programmes qui le sont. Chaque type de compression demande un unpackeur
spécifique.
Exemples : UPX, AsPackDie, …


Cours n°0 : Pour débuter

Cours n°0

 Pour débuter

Qu'est-ce qu'un crack ?

Un crack est un programme qui modifie des octets d'un fichier pour, par exemple, jouer sans le CD, pouvoir s'enregistrer sans avoir le vrai serial...

Ce qu'il vous faut pour cracker :

- WinDasm (v8.9 ou supérieure de préférence).

- Un éditeur hexadécimal (celui que vous préférez)

- Un patcheur (obligatoire seulement si vous souhaitez diffuser vos cracks sur le Net)

- Un cerveau en état de marche.

- De la patience.

Utilisation des outils :

WinDasm : c'est un désassembleur/débuggeur (nous allons au départ nous intéresser uniquement au mode désassemblage). Il sert à voir le code source d'un exécutable (*.exe en général) ou d'une DLL.
Après l'avoir téléchargé, lancez-le (mais non pas par la fenêtre)... J'entends déjà des personnes s'exclamer : "oh non! C'est en anglais". Ne vous inquiétez pas, n'importe quelle personne ne parlant pas anglais peut facilement l'utiliser.
Bon tout d'abord cliquez sur "Disassembler" >>> "Font..." >>> "Select Font" et choisissez la police que vous voulez (une des mieux étant Courrier New 8pt) sinon vous vous retrouverez avec des signes illisibles, cliquez sur "Ok", puis cliquez de nouveau sur "Disassembler">>> "Font..." >>> "Save Default Font" pour, vous l'aurez compris, sauvegarder la police afin de ne pas avoir besoin de refaire cette manip lors d'une prochaine utilisation. Ensuite cliquez sur "Disassembler" >>> "Disassembler Options" et cochez les 3 cases.
Maintenant ouvrez n'importe quel *.exe pas trop lourd, car c'est un peu long à charger. Et là vous allez me dire : "mais qu'est-ce que c'est ce machin? J'comprends rien". Mais c'est normal. Le langage que vous voyez dans WinDasm est de l'Assembleur (appelé également "asm").

Attention : WinDasm refusera d'ouvrir les fichiers qui sont trop enfouis dans de multiples dossiers et sous-dossiers. Dans ce cas il faudra rapprocher les fichiers à désassembler le plus près de la racine de votre disque dur (par exemple dans c:\ ).

Je vais vous expliquer tout d'abord l'usage des boutons:

1° Ouvrir un exécutable pour le désassembler.
2° Sauvegarder les pages du programme désassemblé, ce qui permet de le réouvrir beaucoup plus rapidement la fois suivante. Pour l'ouvrir cliquez sur "Project" >>> "Open Project File...".
3° Rechercher du texte.
4° Permet de copier le texte une ligne, pour cela il faut d'abord cliquer à gauche de la ligne afin d'obtenir un point rouge. Utilisez la touche MAJ pour sélectionner plusieurs lignes à la fois.

5° Aller directement au début du code (il y a, au-dessus, des informations qui n'en font pas parti)
6° Aller au point d'entrée du programme.
7° Aller à la page choisie (le numéro de page étant inscrit tout en bas à gauche).

8° Aller à l'offset choisi.
9° Permet d'aller là où le saut "atterrit".
10° Permet, après avoir utilisé le 9°, de revenir au saut.
11° Permet de rentrer dans le CALL.
12° Permet, après avoir utilisé le 11°, de sortir du CALL.
13° Lister et trouver les fonctions et modules importés (les APIs) du fichier désassemblé.
14° Lister et trouver les fonctions exportées.
15° Permet de voir les données du fichier en hexa et ascii.
16° Permet de voir les données de la section code du fichier affichée.
17° Permet de trouver les items des menus (ex : "copier", "coller", "nouveau"...).
18° Permet de trouver les dialogs du programme.
19° Un des plus importants. Il permet de trouver les Strings Data References c'est à dire les messages des boîtes de dialogues (ex : "Code invalide")
20° Permet d'imprimer des pages (fonction très utile pour pouvoir étudier le programme au bord d'une piscine ;-).

Éditeur hexadécimal : (HexDecCharEditor pour l'exemple, mais ils fonctionnent pratiquement de la même façon) : c'est un outil avec lequel on peut modifier des octets de n'importe quel fichier. C'est pour ça qu'il vous faut un éditeur hexadécimal et WinDasm si vous voulez cracker. Ce dernier sert à voir ce qu'il faut modifier et l'éditeur permet d'effectuer les modifications.
On peut quelques fois cracker avec pour seul compagnon un éditeur hexadécimal (ex : pour enlever les nag-screens, boîtes de dialogue où on peut voir le plus souvent : "Vous n'êtes pas enregistré", ou pour pouvoir utiliser une commande de menu qui ne peut normalement pas être utilisée car grisée) On peut également se servir uniquement de WinDasm pour trouver les bons serials en utilisant le mode débuggeur (je vous expliquerais ça plus tard si vous êtes sage et si vous n'avez pas trop mal à la tête ;-).
Je ne vais pas vous expliquez beaucoup de choses sur cet éditeur hexa car son fonctionnement est assez basique.
Si vous souhaitez rechercher quelque chose cliquez sur [image], une fenêtre s'ouvre (c'est beau la technologie non ?) :

- Tapez dans 'Hex' le code hexadécimal que vous voulez modifier.
- 'Dec' sert à rechercher du code décimal ce qui est très rarement utilisé.
- 'Char' permet de trouver du texte. Il peut être utile quand on veut cracker uniquement avec l'éditeur.

Voilà dans le cours suivant je vous expliquerai l'hexadécimal et les sauts conditionnels.


Cours n°1 : L'hexadécimal et les sauts

Cours n°1

L'hexadécimal et les types de sauts

Comprendre l'hexadécimal

Tout d'abord ouvrez un programme avec WinDasm (si vous ne savez pas l'utiliser allez voir le cours précédent). Vous devez apercevoir des lignes semblables à celle-ci :

Exemple :

:0040100E | E839000000 | Call 0040104C
:00401013 | 83F807 | cmp eax, 00000007
:00401016 | EB1A | jmp 00401032
:00401018 | 6A40 | push 00000040

Le numéro en bleu est l'adresse de la ligne pour WinDasm.
Le nombre en blanc est l'instruction en hexadécimal correspondant aux instructions rouges. C'est cela qu'on recherchera dans l'éditeur hexadécimal et qu'on modifiera.
Les mots et nombres en rouge sont les instructions en assembleur du programme désassemblé. C'est en fait la "traduction" de l'instruction en hexadécimal en blanc.

En décimal vous comptez comme ça (ou du moins je l'espère...) :

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, ...

Et bien en hexadécimal il y a non pas 10 chiffres mais 16 (les 10 du décimal + les 6 lettres A,B,C,D,E,F) on compte donc de cette manière :

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, ...

Donc par exemple 15 en décimal s'écrira F en hexa, 16 => 10, 17 => 11, 26 => 1A, etc...

Les adresses de WinDasm sont en hexadécimal et dans l'ordre croissant, mais il ne les met pas toutes comme on peut le voir dans l'exemple ci-dessus. C'est tout simplement car l'adresse correspond en fait non pas à l'instruction mais au premier octet de l'instruction (un octet est composé de 2 chiffres hexadécimal) donc par exemple à l'adresse 401016 "EB1A" comporte 2 octets ainsi l'instruction "6A40" aura pour adresse 401016+2 soit 401018.

Qu'est-ce qu'un saut ?

Je vais en premier lieu vous montrer le schéma d'une protection utilisée la plupart du temps. Une fois le code rentré il le compare avec le bon code, si c'est pas le même il saute pour arriver vers le mauvais message sinon il affiche le bon message et continue sa route vers de nouvelles aventures en passant au dessus du mauvais message.

JNE est un saut conditionnel. Il saute si le code est faux dans ce cas là (saut si inégal) ou continue sa route si le code est bon.
JMP ( = Jump = Saut) est aussi un saut mais inconditionnel puisqu'il saute obligatoirement, même au bord d'un précipice il sautera quand même :-P ...

Et ça donne à peu près ça en assembleur (les adresses sont fictives et je n'ai pas mis l'intégralité de ce passage comme notamment les instructions pour afficher les messages pour ne garder que le nécessaire) :

:00401001 | #### | Teste si le code est bon
:00401002 | 75## | jne 00401003

Le JNE saute uniquement si le code n'est pas bon.

* Possible StringData Ref from Data Obj ->"Le code est bon"

Windasm indique que "Le code est bon" est le message d'une boîte de dialogue.

:00401003 EB## jmp 00401005

Le JMP est un saut. Le numéro à sa droite est l'adresse où il va sauter.

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401002(U)

Il indique que la portion de code suivante est appelée par un saut conditionnel (l'adresse de celle-ci est à gauche du "(U)" : 00401002)

:00401004 #### push ####

* Possible StringData Ref from Data Obj ->"Le code n'est pas bon"

:00401005 #### ####

Après ces schémas, vous avez donc compris (du moins je l'espère) que c'est le jne qu'il faut changer. Mais vous allez me dire :

Comment change-t-on un saut conditionnel ?

Regardez le schéma en assembleur et plus précisément la ligne 00401002 avec le jne. Dans le code hexa, on voit 75##. Et bien le 75 c'est notre jne. Tout comme le jmp qui correspond à EB.

Voici une petite liste de sauts conditionnels que vous pourrez trouver le plus souvent :

Instruction | Valeur hexa | Description
jne | 75 ou 0F85 | Saut si inégal
je | 74 ou 0F84 | Saut si égal
jmp | EB ou E9 | Saut
nop | 90 | Pas d'opération

Pour modifier un saut, il suffira donc de rechercher sa valeur et de la modifier avec l'éditeur hexadécimal. Par exemple pour le schéma ci-dessus on mettra un je (il sautera à "Le code est bon" si le code est mauvais) ou nop (il ne sautera pas du tout et ira donc directement vers "Le code est bon") à la place de jne.

Pour information les 75, 74, EB sont des sauts courts et 0F85, 0F84, E9 des sauts longs. Après les sauts courts on a le nombre (en hexa) d'octets sautés (un octet est un nombre de 2 chiffres qui va donc en hexa de 00 à FF comme peut l'être 75 ou 90) et donc pour les sauts de plus de FF (soit 255 en décimal) octets on utilise des sauts longs à la suite desquelles on met l'adresse de destination. Par exemple EB02 représente un saut (JMP) de 2 octets.

Après la théorie place à la pratique, dans le cours n°2 nous essaierons de cracker un crack-me.


Cours n°2 : Cracker un crack-me

Cours n°2

Cracker un crackme

Objectif :

Cracker un crack-me en modifiant un saut.

Niveau :

Très débutant

Ce qu'il vous faut :

- WinDasm

- Un éditeur hexadécimal

- Le crack-me de 3 Ko (et oui, c'est mieux de l'avoir si vous voulez le cracker ;-)

Allez go !

Le but est donc de le cracker pour arriver sur un bon message quand on clique sur OK. Il faut donc modifier un saut.

Après avoir téléchargé ce crack-me, lancez-le pour voir comment il se présente et relevez le titre du message d'erreur et son contenu.

Désassemblez ensuite ce crack-me avec WinDasm et cliquez sur [image] pour trouver le message d'erreur. Double-cliquez sur ce message ("Ahhh! Tu n'as pas reussis" ou "FATAL ERROR !!" - titre du message). Vérifiez qu'il n'y en a qu'un seul en double-cliquant encore une fois (dans certains programmes le message peut apparaître plusieurs fois, mais là ce n'est pas le cas).

Remontez un peu et là on aperçoit juste avant le message :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401016(U)

Le message est donc appelé par un saut à l'adresse 00401016. On va donc à cette adresse soit en remontant les lignes ou en la recherchant (avec F12 ou "Goto Code Location").

Arrivé à cette ligne, on voit un JMP :

:00401016 EB1A jmp 00401032

Pour atteindre la ligne 00401032 (où il saute), on double-clique sur cette ligne pour la sélectionner (elle deviendra verte) et on clique sur [image] pour arriver à la ligne où il va.

Pour revenir au JMP on clique sur [image]. Il arrive juste avant le mauvais message. Si on regarde un peu en dessous du JMP, on voit un message qu'il saute et qui à l'air d'être le bon message :

* Possible StringData Ref from Data Obj ->"Okay Dokey!"

Vous l'avez donc sûrement compris, il faut donc changer ce vilain jmp et le remplacer par un nop (pas d'opération = pas de saut). On relève donc le code en hexa de la ligne du jmp ("EB1A"). On ouvre le crack-me avec l'éditeur hexadécimal et il faut rechercher "EB1A". Mais il peut, pour certains programmes, y avoir des dizaines de "EB1A". Donc on prend le code hexa des instructions qui se retrouvent avant et après le JMP (on recherchera donc : "83F807EB1A6A40" et on vérifiera qu'il n'y en a pas plusieurs). Autre méthode plus pratique à mon goût pour retrouver EB1A : on peut se repérer avec l'offset, vous trouvez son numéro tout en bas dans la barre d'état quand vous sélectionnez la ligne voulue :

Code Data @:00401016 @Offset 00000616h in File ....etc....

Dans ce cas "616" est l'offset, pas besoin des zéros avant le nombre et le "h" indique que c'est en hexadécimal. Pour se rendre à cette adresse dans HexDecCharEditor on fait "Edit" --> "Go To..." ou CTRL+T. Et on tape dans la case "Hex" (car l'offset est en hexadécimal) : 616, on valide et on se retrouve directement sur le code qu'on souhaite modifier.

Arrivé là où il faut, on remplacera EB1A par 9090 ("nop nop"). On enregistre, on le lance et là... ça marche !

Attention : si le programme à modifier est déjà ouvert ou désassemblé avec WinDasm vous ne pourrez pas enregistrer le fichier par dessus. Dans ce cas il faut mieux enregistrer sous un autre nom, ce que je vous conseille de faire à chaque fois pour avoir une sauvegarde de l'original. Mais n'oubliez pas de mettre le ".exe" à la fin du nom du fichier.

J'espère que vous avez tout compris. Si vous ne comprenez pas ou si je ne suis pas clair dans mes explications,
n'hésitez pas à me contacter à cette adresse : deamon.crack (at) netcourrier.com


Cours n°3 : Cracker un crack-me

Cours n°3

Cracker un crack-me

Objectif :

Cracker un crack-me pour qu'il nous dise que le code est bon, alors qu'on a tapé un faux serial.

Niveau :

Très débutant

Ce qu'il vous faut :

- WinDasm

- Un éditeur hexadécimal

- Le crack-me de 20 Ko (toujours autant utile :-)

Allez go !

Tout d'abord lancez le logiciel. Il vous demande de taper le sérial. Tapez un nombre et cliquez sur "Check", il vous dira "Incorrect try again!!" ou alors si le crackme vous met que le code est bon courez vite jouer au loto. Notez le message, puis désassemblez le programme avec WinDasm. Regardez les "StringsData References". Vous avez de la chance il y en a que 4. Double-cliquez sur le message que vous avez noté (regardez s'il n'y en a pas plusieurs en double-cliquant une autre fois dessus). Vous devriez arriver là :

:0040158D FF1500204000 Call dword ptr [00402000]
:00401593 85C0 test eax, eax << Vérifie si le sérial est bon ou non
:00401595 7516 jne 004015AD << Saute vers "Incorrect..." s'il est faux sinon continue à "Correct..."
:00401597 6A40 push 00000040

* Possible StringData Ref from Data Obj ->"CrackMe"
:00401599 6850304000 push 00403050
* Possible StringData Ref from Data Obj ->"Correct way to go!!" << Message bon
|
:0040159E 6858304000 push 00403058
:004015A3 8B4DE0 mov ecx, dword ptr [ebp-20]

* Reference To: MFC42.MFC42:NoName0077, Ord:1080h
|
:004015A6 E853050000 Call 00401AFE
:004015AB EB14 jmp 004015C1 << Saute au-dessus de "Incorrect..."

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401595(C)
|
:004015AD 6A40 push 00000040

* Possible StringData Ref from Data Obj ->"CrackMe"
:004015AF 686C304000 push 0040306C
* Possible StringData Ref from Data Obj ->"Incorrect try again!!" << Message d'erreur
|
:004015B4 6874304000 push 00403074
:004015B9 8B4DE0 mov ecx, dword ptr [ebp-20]

Donc vous l'aurez compris, du moins si vous avez compris les cours précédents, on peut donc remplacer le saut JNE en JE, mais quand vous taperez le bon code, il vous dira que le code est faux. Il faut donc mieux mettre un NOP (pas d'opération) et donc il pourra continuer vers "Correct...".

On ouvre le crack-me avec l'éditeur hexadécimal, on se rend à l'offset de la ligne du 7516 et on remplace 7516 par 7416 (pour un JE) ou par 9090 (pour deux NOP). On enregistre, on le lance on tape n'importe quel nombre et là : "Correct way to go!!"

Et voilà !! C'est aussi simple que ça. Dans le prochain cours on utilisera la fonction débuggeur de WinDasm pour trouver le vrai sérial sans rien modifier.


Cours n°4 : Trouver le bon sérial

Cours n°4

Trouver le bon serial

Objectif :

Trouver le bon sérial d'un crack-me

Niveau :

Débutant

Ce qu'il vous faut :

- WinDasm

- Un éditeur hexadécimal

- Le crack-me de 20 Ko (le même que le cours précédent)

C'est parti !

Avant de commencer je précise que cette méthode ne fonctionne que très rarement quand on crack mais existe néanmoins et de plus les résultats seront différents suivant la version de WinDasm que vous utilisez. Donc il se peut que cela ne marche pas avec votre WinDasm. Si c'est le cas lisez juste le cours sans le mettre en pratique ce n'est pas grave.

Nous allons dans ce cours nous intéresser de près au débuggeur de WinDasm, qui nous permettra de trouver le bon mot de passe pour ce crack-me.
Pour commencer désassemblez-le et regardez un peu au-dessus du bon message :

* Reference To: KERNEL32.lstrcmpA, Ord:02FCh
|
:0040158D FF1500204000 Call dword ptr [00402000] <<< Intéressant !!!
:00401593 85C0 test eax, eax
:00401595 7516 jne 004015AD
:00401597 6A40 push 00000040

* Possible StringData Ref from Data Obj ->"CrackMe"
|
:00401599 6850304000 push 00403050

* Possible StringData Ref from Data Obj ->"Correct way to go!!"

Il va falloir placer un "Break Point" en 0040158D (c'est-à-dire que le programme sera mis en pause quand il arrivera à cette adresse) sur le CALL car il est juste avant la comparaison (test eax, eax). Donc retenez l'adresse précédente (0040158D) et lancez le débuggeur en tapant CTRL+L ou en cliquant sur "Load Process" du menu "Debug". Là une boîte de dialogue vous propose de taper une commande (cette fonction ne sert pratiquement jamais) ne tapez rien et cliquez sur "Load". Vous avez maintenant trois fenêtres : l'écran principal de WinDasm (en haut), une pour les DLL, eax, ebx...(qui ne nous servira pas aujourd'hui) et une autre avec les adresses et les APIs (à droite). Vérifiez que dans cette dernière les 5 cases soient cochées.

Dans l'écran principal cliquez sur "Goto Code Location" et tapez l'adresse que vous avez dû retenir en l'écrivant sur un petit bout de papier (0040158D pour ceux qui n'avaient pas de quoi écrire ;-) ). Arrivé à cette ligne, posez un "Break Point Toggle" avec F2 ou en cliquant dans le menu "Debug" sur "Break Point Toggle" (la ligne deviendra jaune ou avec un carré jaune à sa gauche si la ligne est déjà sélectionnée ce qui doit être le cas). Dans la 3ème fenêtre, cliquez sur "Run" ou F9 pour lancer le crack-me. Tapez un sérial bidon (ex :123456789) et appuyez sur "Check". Là, normalement, il va breaker et une fenêtre va s'ouvrir. Regardez-là ! (La fenêtre peut ne pas apparaître suivant les versions de WinDasm).

API int Arg00 = lstrcmp(Arg01,Arg02)
API Address=0040158D, API Return Address=00401593
Arg01 = (LPCTSTR) 0063f810 -> "123456789" <<< Tiens ! Ça ne serait pas le sérial qu'on vient de taper ???
Arg02 = (LPCTSTR) 0063f800 -> "<BrD-SoB>" <<< Mais qu'est-ce donc ??? ;-)

Vous l'aurez tous compris, le "<BrD-SoB>" est le véritable serial (Attention mettez bien les < et >).
Pour vérifier, fermez d'abord le débuggeur. Cliquez sur "Close", puis sur "Terminate" (au-dessus de "RUN") et lancez le crack-me. Tapez le code que vous avez trouvé, et là... "Correct way to go!!"

Mission accomplie ! Vous (enfin nous) avez réussi à trouver le bon code. Mais ne vous enflammez pas en disant à vos amis que vous êtes un cracker expert, car ce crack-me est vraiment facile et vous verrez dans les prochains cours qu'il y en a qui sont vraiment plus durs que celui-là. Et vous verrez très peu de bons crackers se vantant de leurs exploits. Alors à la prochaine !


Cours n°5 : A mort le crack-me !

Cours n°5

A mort le crack-me !

Objectif :

1° Le cracker.
2° Trouver le bon serial du crack-me
3° Faire un keygen.

Niveau :

1° Débutant
2° et 3° Moyen

Ce qu'il vous faut :

- WinDasm

- Un éditeur hexadécimal

- Le crack-me de 163 Ko

- Un éditeur Basic (870 ko) pour faire le Keygen. (facultatif)

A l'attaque !

Me voici de retour après quelques mois sans nouveaux cours. Dans ce cours il va falloir cracker ce crack-me pour qu'il nous dise que le code est bon même s'il est faux, il va falloir également trouver le serial (comme dans le cours précédent) avec WinDasm et enfin créer un keygen en Basic. Bref on a du boulot ! ;-)

Qu'est-ce qu'un keygen ?

C'est un programme qui vous demande votre nom et vous donne le mot de passe du programme correspondant à votre nom. On en trouve peu par rapport aux cracks ou serials, car il faut trouver comment le programme calcule le serial en fonction du nom, ce qui est plus complexe. Mais dans ce crack-me c'est facile à voir. Le keygen va être programmé en Basic : c'est un langage très simple qui fait des programmes DOS en *.exe. Mais vous pourrez utiliser n'importe quel autre langage de programmation.

Commençons par le cracker !

Comme d'habitude on exécute le crack-me pour voir de quoi il a l'air. Il nous demande un nom et un serial, on tape n'importe quoi et il nous dit "Error" - "Wrong Code !". Si vous regardez dans "About" l'auteur nous dit qu'il ne faut pas le patcher mais bon nous on fait ça pour apprendre. Donc on ouvre WinDasm et on désassemble le crack-me. On clique sur "StringsData References", on recherche le message d'erreur ("Wrong Code") et on voit ça :

:00441756 8B55F8 mov edx, dword ptr [ebp-08]
:00441759 58 pop eax
:0044175A E8E523FCFF call 00403B44
:0044175F 7517 jne 00441778 <<< Intéressant !!!
:00441761 6A00 push 00000000
:00441763 668B0DD8174400 mov cx, word ptr [004417D8]
:0044176A B202 mov dl, 02

* Possible StringData Ref from Code Obj ->"Right Code" <<< Message bon
|
:0044176C B8E4174400 mov eax, 004417E4
:00441771 E802FBFFFF call 00441278
:00441776 EB15 jmp 0044178D <<< Si le code est bon il saute au dessus du message d'erreur.

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0044175F(C) <<< On arrive au message d'erreur par un saut qui est à cette adresse.
|
:00441778 6A00 push 00000000
:0044177A 668B0DD8174400 mov cx, word ptr [004417D8]
:00441781 B201 mov dl, 01

* Possible StringData Ref from Code Obj ->"Wrong Code" <<< Message d'erreur
|
:00441783 B8F8174400 mov eax, 004417F8
:00441788 E8EBFAFFFF call 00441278

Il y a un JNE avant le bon message qui saute, si le code est faux, au message d'erreur, il faut donc l'empêcher de sauter. On peut mettre un JE à la place de JNE mais si le code est juste il dira qu'il est faux (vous me suivez ?) : en clair il fera l'inverse. Donc il vaut mieux mettre un NOP (pas d'opération) et donc il ne sautera pas et passera alors par le bon message.

Bon on ouvre le crack-me avec l'éditeur hexadécimal et on change 7517 qu'on remplace par 9090. (Si vous ne savez pas utiliser l'éditeur allez voir les premiers cours).

Normalement vous devriez avoir tout compris.... ou alors j'explique mal. Si vous n'avez pas compris relisez le début de ce cours ou les anciens cours avant de lire la suite. Et si vous ne comprenez vraiment pas écrivez-moi à deamon.crack (at) netcourrier.com.

Trouver le bon serial !

Si vous avez lu et compris le cours précédent vous ne devriez pas avoir de problèmes pour trouver le bon serial.

Désassemblez le programme avec WinDasm, recherchez le message d'erreur et regardez bien :

:00441751 E89E23FCFF call 00403AF4
:00441756 8B55F8 mov edx, dword ptr [ebp-08]
:00441759 58 pop eax
:0044175A E8E523FCFF call 00403B44 <<<C'est ici qu'il faudra breaker
:0044175F 7517 jne 00441778 <<< Saute vers le bon ou le mauvais message
:00441761 6A00 push 00000000
:00441763 668B0DD8174400 mov cx, word ptr [004417D8]
:0044176A B202 mov dl, 02

* Possible StringData Ref from Code Obj ->"Right Code"
|
:0044176C B8E4174400 mov eax, 004417E4
:00441771 E802FBFFFF call 00441278
:00441776 EB15 jmp 0044178D

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0044175F(C)
|
:00441778 6A00 push 00000000
:0044177A 668B0DD8174400 mov cx, word ptr [004417D8]
:00441781 B201 mov dl, 01

* Possible StringData Ref from Code Obj ->"Wrong Code"

Comme vous avez vu il faudra poser un Break Point sur le Call qui est juste avant le JNE qui va vers le bon ou le mauvais message (le plus souvent le CALL à étudier est juste au-dessus du saut). Donc retenez l'adresse du Call (0044175A). On lance le mode débuggeur en cliquant sur "Load Process" du menu "Debug" ou en faisant CTRL+L. Cliquez ensuite sur "Load". Dans la fenêtre de droite vérifiez que toutes les cases soient cochées. Allez à l'adresse du Call en faisant "Goto Code Location" et posez un "Break Point" en double-cliquant sur la ligne voulue (celle du Call) et en appuyant sur F2 ou en cliquant à gauche de la ligne avec le bouton CTRL enfoncé. Il doit y avoir désormais un carré jaune à gauche de la ligne.

Lancez le crack-me en cliquant sur "Run" (à droite) ou en appuyant sur F9. Tapez votre nom (Deamon pour moi mais tout le monde ne s'apppelle pas forcément Deamon) et un serial bidon (ex : 123456789) et cliquez sur "Ok". Il va biper car il s'est arrêté au Call et une fenêtre apparaît où on peut voir le nom qu'on a mis (Deamon) le serial bidon en bas (123456789) et entre les deux .... le serial : DeamonDeamon625g72 . Cliquez sur "Terminate" pour fermer le débuggeur et réessayez la même opération avec un autre nom ("Crack" par exemple). Vous trouverez alors CrackCrack625g72. On comprend donc qu'en fait le crack-me met 2 fois votre nom et rajoute "625g72". Ce sera donc facile de créer un keygen.

Faire un keygen en Basic

Lancez l'éditeur de Basic en double-cliquant sur QB.EXE ou son raccourci QB. Et oui c'est moche mais c'est normal c'est du DOS, c'est sur que ça vous change des beaux programmes sous Windows. ;-)

Voici le code source à taper (en bleu) :

1° CLS
2° PRINT "Keygen pour le crack-me n°3"
3° PRINT
4° INPUT "Quel est votre nom"; A$
5° PRINT "Salut "; A$; " ! Ton serial est : "; A$; A$; "625g72 !"

Explication :

1° Sert à rendre la fenêtre vierge dans laquelle va apparaître le message.
2° PRINT affiche le message, qui est entre parenthèses, à l'écran.
3° Rien à afficher : pour sauter une ligne.
4° INPUT sert à poser des questions où on peut répondre. Il faut mettre la question entre parenthèses, ne pas mettre de "?" (il le met tout seul) et mettre "; x$" après le message (on peut remplacer "x" par ce qu'on veut). Le "x$" est le nom de la réponse, que l'on met, pour l'ordinateur.
5° Affiche le message et "A$" qui est la réponse. Donc si vous répondez "Deamon" A$=Deamon (ATTENTION : il ne faut pas mettre "x$" entre parenthèses).

Vous pouvez à tout moment essayer votre programme en faisant MAJ+F5 ou en cliquant sur "Démarrer" dans "Exécution".
Ensuite enregistrez votre code source dans "Enregistrer sous..." du menu "Fichier". Pour le mettre en EXE il faut cliquer sur "Créer un fichier EXE" dans "Exécution" (mettez-le en fichier EXE autonome). Et voilà le keygen est créé dans le dossier de l'éditeur Basic.
J'espère que vous avez compris les commandes, sinon allez voir l'aide de l'éditeur ou regardez les autres programmes qui sont avec (bien que beaucoup ne marchent pas).

Attention : sous Windows XP il semblerait que le keygen créé se ferme automatiquement, dans ce rajoutez : "INPUT "Appuyer sur ENTRER pour terminer"; A$"

Si la création du keygen ne marche pas laissez tomber, c'est juste pour s'amuser que j'ai rajouté ce paragraphe. Les keygens habituellement sont beaucoup plus complexes que celui-ci donc il faut obligatoirement connaitre un langage de programmation pour faire un keygen.


Cours n°6 : WinRar 2.71

Cours n°6

WinRAR 2.71

Objectif :

1° Le cracker :
- Virer le Nag Screen
- Remplacer le "40 days trial copy" par votre nom ou mettre "registered version"
- Enlever le "(evaluation copy)" qui est dans la barre de titre.

2° Fabriquer le crack

Niveau :

1° Débutant - Moyen
2° Trop facile

Ce qu'il vous faut :

- WinDasm

- Un éditeur hexadécimal

- WinRAR 2.71

Virer le Nag Screen :

Et voici le 6ème cours que j'écris et cette fois on va s'attaquer à un vrai logiciel plutôt qu'à un crack-me. Et ce logiciel tout le monde doit le connaître : c'est WinRar, le célèbre décompresseur. La version est la 2.71 et celle en anglais (us).

Donc on installe WinRAR on le lance et là au bout de quelques secondes apparaît un nag screen (pour ceux qui savent pas ce que c'est, c'est un message qui apparaît pour nous dire qu'on est pas enregistré ou par exemple que le code n'est pas bon). Celui-ci nous dit "Please note that WinRAR is shareware", ça alors, dans quelques minutes il ne le sera plus. Le titre du message est "Please registrer", très original pour un shareware. Donc à ce moment on va laisser tomber les WinDasm et autres désassembleurs et on va plutôt prendre l'éditeur hexadécimal.

On ouvre winrar.exe avec l'éditeur et on recherche notre message. Donc on va rechercher du texte (faîtes "rechercher" et taper "Please" avec la majuscule dans la partie recherche de texte 'Char'). Là bizarrement on ne trouve rien, mais c'est normal car les messages comme le notre sont marqués avec des blancs (00 en hexa) entre les lettres.

Ex : Please --> 50 6C 65 61 73 65 et avec les blancs (ce que j'appelle des blancs ce sont des vides (00) et surtout pas des espaces (20)) cela fait P l e a s e --> 50 00 6C 00 65 00 61 00 73 00 65

Pour mettre des blancs avec HexDecCharEditor par exemple on tape "Please", on sélectionne le "e" et on appuie sur la touche "Inser" puis on fait pareil pour les autres lettres. J'espère que que vous avez compris jusque là.

Bon, on trouve 3 "Please" le premier c'est le titre de notre message, le 2ème c'est le message lui-même et le 3ème c'est un autre Please qui ne nous intéresse pas du tout. Alors regardons le 2ème et juste avant le "P" de Please on voit 82 00. Ce qui nous intéresse c'est le 82, c'est ce qui déclenche le message. On remplace donc le 82 par 7E. Me demandez pas pourquoi on met 7E, c'est comme ça. On enregistre et là le Nag screen a disparu. C'est pareil pour la plupart des logiciels qui ont des nags screen, il faut remplacer le 82 qui est un peu avant le début du message par 7E.

Remplacer le "40 days trial copy" par votre nom ou par "Registered Version" :

Bon alors là c'est facile et ça va donc être vite fait. On garde encore l'éditeur hexadécimal sous la main ou plutôt sous le curseur et on recherche "40 days evaluation copy" (tapez pas toute la phrase, mettez juste le début ou un nom précis). Donc on recherche "days" par exemple. Si vous avez bien suivi ce que j'ai expliqué vous devez trouver le message. Sinon regardez la partie du cours au dessus. Je vous ai dit que pour la plupart des messages il y avait des blancs entre les lettres. Donc on recherche "d a y s" (voir ci-dessus). Il y en a deux dans tout le programme mais il n'y a que le premier avec "40 days". Et donc là vous pouvez mettre ce que vous voulez, en ne mettant surtout pas plus de 18 caractères (nombre de lettres + les espaces de "40 days trial copy") car il ne faut jamais rajouter de bytes. Faites attention aussi à ne pas effacer les blancs (00). Mettez par exemple "Registered Version" qui fait pile 18 lettres (j'aurais bien voulu le mettre en français "version enregistrée" mais ça fait 1 lettre de trop :-( ). Et voilà, passons à la suite voulez-vous.

Enlever le vilain "(evaluation copy)" qui est dans la barre de titre :

Passons à la suite qui est un peu plus dur. Vous voyez dans la barre de titre : "WinRAR - WinRAR (evaluation copy)" ce qui fait un peu désordre pour un de vos logiciels. Donc on va d'abord essayer de l'effacer avec l'éditeur hexadécimal. On recherche donc "evaluation copy" avec les blancs bien sûr. Et là on met des espaces (20h) à la place des lettres sans remplacer les blancs (00h) par les espaces (20h). On enregistre (n'effacez pas la dernière version car on en aura besoin bientôt) et on lance armstrong (calembour :-P ), plus sérieusement on lance le programme, mais là je ne sais pas si c'est à cause de ma vanne foireuse mais il y a un problème ! Les parenthèses restent toujours mais pourtant on ne les a pas vues dans l'éditeur. On va donc ("enfin !" me diront certains) avoir besoin de WinDasm.

On désassemble WinRAR, et on recherche "evaluation copy" dans "search" --> "Find text". Il en trouve un seul. Et on peut voir :

:0041BA15 803DCC6C460000 cmp byte ptr [00466CCC], 00
:0041BA1C 752E jne 0041BA4C <-- JNE qui saute au dessus d'evaluation copy

* Possible Reference to String Resource ID=00873: "evaluation copy" <-- Notre "evaluation copy"
|
:0041BA1E 6869030000 push 00000369
:0041BA23 E894C6FEFF call 004080BC
:0041BA28 50 push eax

* Possible StringData Ref from Data Obj ->" (%s)"
|
:0041BA29 68E95F4600 push 00465FE9
:0041BA2E 8D8D00FEFFFF lea ecx, dword ptr [ebp+FFFFFE00]
:0041BA34 51 push ecx
:0041BA35 E8AACA0300 call 004584E4
:0041BA3A 59 pop ecx
:0041BA3B 8D9500FEFFFF lea edx, dword ptr [ebp+FFFFFE00]
:0041BA41 03C2 add eax, edx
:0041BA43 50 push eax
:0041BA44 E84B0B0400 call 0045C594
:0041BA49 83C40C add esp, 0000000C

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0041BA1C(C)
|
:0041BA4C 8D8500FEFFFF lea eax, dword ptr [ebp+FFFFFE00] <-- Là où arrive le JNE
:0041BA52 50 push eax

Inutile de vous dire donc ce qu'on va faire, hein ? Bon allez je vous le dis quand même pour ceux qui n'aurais pas compris. On va faire un barbecue ! Roh mais que tu es naïf... Il faut tout simplement remplacer le JNE par JE, donc 75 en 74 avec l'éditeur hexadécimal.

Fabriquer le crack :

Alors là c'est vraiment simple. Vous voyez ce que c'est simple ? Et bien encore plus simple ! Vous lancez un patcheur tel que WinPatchEngine par exemple, vous sélectionnez le fichier original et celui cracké, vous tapez le nom du programme, votre nom... Et vous appuyez sur "Compare" et là vous avez un beau crack tout neuf fait par vous (enfin presque vous car je vous ai aidé un peu quand même, non ?) et vous pouvez le diffuser sur le net.


Cours n°7 : Cracker un crack-me

Cours n°7

Cracker un crack-me

Objectif :

Cracker un crackme (original non ? Tout comme le titre du cours d'ailleurs... Manque d'inspiration me diront certains...)

Niveau :

Débutant

Ce qu'il vous faut :

Le kit du parfait petit crackeur comme d'habitude qui comprend :

- WinDasm

- Un éditeur hexadécimal

- Le crack-me (10 Ko)

Attention la patience et la réflexion ne sont pas comprises dans ce kit, vous devrez les apporter vous-même. Bien que pour ce qui est de la réflexion je vous aiderai un peu. ;-)

C'est parti !

Pour ce 7ème cours nous allons nous intéresser à un crack-me avec un schéma de protection un peu différent des autres mais pas trop dur non plus. Nous allons simplement le cracker pour qu'il nous dise toujours "code bon".

Donc on lance le crack-me, on fait "Help" puis "Register", on tape son nom et un serial bidon et on clique sur Ok. Et là apparaît un vilain message : "No luck" - "No luck there, mate !". On note le message comme d'habitude et on désassemble le crack-me avec notre fameux WinDasm. Ah qu'est-ce qu'on ferait si on ne l'avait pas celui-là ? On prendrait OllyDbg oui je sais mais à l'époque y avait que WinDasm ! ;-)

On va dans les "Strings Data References" et on trouve facilement notre message d'erreur car il n'y a pas 36 messages fort heureusement. On double-clique dessus pour arriver à l'endroit où il se trouve. On double-clique encore plusieurs fois pour voir s'il n'y en a pas plusieurs et effectivement on en voit deux. Pour savoir lequel il faut prendre on va utiliser le débuggeur de WinDasm. Donc on fait CTRL+L pour le lancer et on va poser un BreakPoint juste avant l'un des deux. On va commencer par le deuxième (pourquoi le 2ème d'abord ? aucune idée ! De toute façon il faut bien en choisir un !). On va au deuxième message d'erreur et on pose un BreakPoint (BP) sur le PUSH juste avant le message (en double-cliquant sur la ligne puis en appuyant sur F2) :

:004013AC 5E pop esi
:004013AD 6A30 push 00000030 <-- C'est ici que l'on va breaker

* Possible StringData Ref from Data Obj ->"No luck!" <-- 2ème message d'erreur
|
:004013AF 6860214000 push 00402160

* Possible StringData Ref from Data Obj ->"No luck there, mate!"
|
:004013B4 6869214000 push 00402169
:004013B9 FF7508 push [ebp+08]

Après avoir poser le BreakPoint, on clique sur "Run" ou F9. On entre un nom ("Deamon" pour moi) et un serial bidon (123456) et on fait Ok. Là le message d'erreur apparaît. Donc WinDasm n'a pas breaké, alors ce ne doit pas être le bon message. On va essayer avec le premier message qu'on avait aperçu. On enlève le BreakPoint (en étant sur la ligne du BP et en faisant F2) et on en place un sur le PUSH qui est juste avant le premier message :

:00401364 E8AD000000 Call 00401416
:00401369 6A30 push 00000030 <-- C'est ici que l'on va breaker

* Possible StringData Ref from Data Obj ->"No luck!" <-- 1er message d'erreur
|
:0040136B 6860214000 push 00402160

* Possible StringData Ref from Data Obj ->"No luck there, mate!"
|
:00401370 6869214000 push 00402169
:00401375 FF7508 push [ebp+08]

Ou plus rapide on pose un BP sur les 2 et on voit où il breake.

On revient dans le crack-me, on entre un nom et un serial, on clique sur Ok et là il break, donc c'est sur ce message qu'il faut travailler (mais vous verrez par la suite que le deuxième message n'est pas là pour rien). On quitte le débuggeur (en cliquant sur "Terminate"), on revient sur le premier message et on regarde un peu au dessus :

* Referenced by a CALL at Address: <-- Le message d'erreur est appelé par un CALL
|:00401245 <-- Adresse du CALL
|
:00401362 6A00 push 00000000

* Reference To: USER32.MessageBeep, Ord:0000h
|
:00401364 E8AD000000 Call 00401416
:00401369 6A30 push 00000030

* Possible StringData Ref from Data Obj ->"No luck!" <-- Message d'erreur
|
:0040136B 6860214000 push 00402160

* Possible StringData Ref from Data Obj ->"No luck there, mate!"
|
:00401370 6869214000 push 00402169
:00401375 FF7508 push [ebp+08]

* Reference To: USER32.MessageBoxA, Ord:0000h
|
:00401378 E8BD000000 Call 0040143A
:0040137D C3 ret

Ici on ne voit pas de saut conditionnel mais on voit que le message d'erreur est appelé par un CALL. Donc on va à l'adresse du CALL (00401245) :

:00401240 58 pop eax
:00401241 3BC3 cmp eax, ebx
:00401243 7407 je 0040124C
:00401245 E818010000 call 00401362 <-- Va au message d'erreur
:0040124A EB9A jmp 004011E6 <-- Saute au-dessus

Donc là on va nopper le CALL et on verra bien si le JMP nous envoie sur le bon message. On ouvre le crack-me avec un éditeur hexadécimal et on remplace E818010000 par 9090909090. On enregistre sous un autre nom par garder l'original et on le lance. Mais là ça ne marche pas. Donc on regarde où on va si on noppe également le JMP. En dessous du JMP il y a :

:0040124A EB9A jmp 004011E6
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401243(C)
|
:0040124C E8FC000000 call 0040134D <-- Va sur le bon message

 Il faut alors nopper également le JMP en remplaçant le EB9A par 9090. Et là ça marche ! Mais cette méthode n'est pas très fine. En effet il y a mieux à faire en changeant un seul octet (un octet est, je le rappelle, composé de deux chiffres hexadécimaux, par exemple 74 ou E8 est un octet). Reprenons la partie où se situe le CALL du mauvais message :

:00401240 58 pop eax
:00401241 3BC3 cmp eax, ebx <-- Compare eax et ebx (donc sûrement notre serial et le bon)
:00401243 7407 je 0040124C <-- Saute si égal sur le CALL du bon message
:00401245 E818010000 call 00401362 <-- Va au message d'erreur
:0040124A EB9A jmp 004011E6

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401243(C)
|
:0040124C E8FC000000 call 0040134D <-- Va sur le bon message

Il suffit donc de mettre un JMP (EB) à la place du JE (74) pour qu'il saute toujours sur le CALL qui envoie sur le bon message. Donc on remplace 7407 par EB07. On teste et c'est bon.

Voilà donc ce crack-me est cracké, mais on va s'amuser à faire autre chose. Quand vous lancez le crack-me cracké et que vous mettez un nombre à la place du nom là il affiche le message d'erreur puis le bon message. Vous vous souvenez du deuxième message d'erreur ? Et bien c'est à cause de celui là qu'il s'affiche (vous pouvez utiliser le débuggeur comme on a fait auparavant pour savoir quel message apparaît, mais en mettant bien sûr un numéro au lieu du nom).

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040138B(C)
|
:004013AC 5E pop esi
:004013AD 6A30 push 00000030

* Possible StringData Ref from Data Obj ->"No luck!"
|
:004013AF 6860214000 push 00402160

* Possible StringData Ref from Data Obj ->"No luck there, mate!"
|
:004013B4 6869214000 push 00402169
:004013B9 FF7508 push [ebp+08]

Comme vous pouvez le constater, le mauvais message n'est pas appelé au téléphone (oui je sais c'est nul) mais par un saut conditionnel. Allons donc voir en 0040138B :

:00401389 3C41 cmp al, 41
:0040138B 721F jb 004013AC <-- Saute juste avant le mauvais message
:0040138D 3C5A cmp al, 5A

Donc là c'est pas trop compliqué on remplace juste le 721F par 9090 pour le nopper. Et voilà. Vous pouvez maintenant vous enregistrer si par exemple vous êtes 007 ou K2000.

Et voilà un autre crack-me qui ne nous a pas trop résisté. J'espère que vous avez tout compris (sinon il faut relire le cours) et à un prochain cours.


Cours n°8 : Teleport Pro 1.29.1820

Cours n°8

Teleport Pro 1.29.1820

Objectif :

Cracker le logiciel Teleport Pro en version 1.29.1820

Niveau :

Moyen

Ce qu'il vous faut :

- WinDasm

- Un éditeur hexadécimal

- Le logiciel Teleport Pro v.1.29.1820 (870 Ko)

- Ne pas être pressé !

C'est parti !

On va s’attaquer dans ce cours à un vrai logiciel. Il s’agit de Teleport Pro (version 1.29.1820). Ce programme est très intéressant à cracker, car il y a plusieurs éléments à modifier.

Tout d’abord on va modifier un "truc" pour éviter que vous vous énerviez (enfin vous je ne sais pas mais moi ça m'énerve :-o ), car le logiciel démarre seulement si l’exécutable se nomme "pro.exe" donc si vous voulez l'enregistrer sous un autre nom (ce que je vous conseille pour garder l’original et de toute façon vous ne pouvez pas enregistrer vos modifications sur le fichier qui est déjà ouvert avec WinDasm), vous seriez obliger de modifier le nom de l’original puis du programme cracké pour voir si ça marche. Ce qui serait pénible à faire chaque fois.

On va donc voir ce qu’il nous dit quand le nom du fichier n’est pas bon. On modifie ainsi "pro.exe" en "pro1.exe" par exemple et on le lance. Là un "beau" message, qu’on va s’empresser d’enlever, apparaît : "This program’s name have been changed; please rename…".
On ouvre WinDasm et on désassemble notre programme. On cherche ensuite notre message dans les Data Strings References et on double-clique dessus. Ce message a une seule référence :

:0040BE79 85C0 test eax, eax
:0040BE7B 59 pop ecx
:0040BE7C 7413 je 0040BE91
:0040BE7E 53 push ebx
:0040BE7F 53 push ebx

* Possible StringData Ref from Data Obj ->"This program's name has been changed; "

En regardant au dessus du message on peut voir un "TEST" suivi d’un saut, ici "JE". Il faut donc remplacer ce JE en JMP soit 74 par EB.

Rappel : Pour trouver plus facilement l’emplacement de ce 74 dans l’éditeur hexadécimal, double-cliquez dans WinDasm sur la ligne du « JE » et regardez en bas de l’écran dans la barre d’état : "Line:23454 Pg 273 of 2470 Code Data @ :0040BE7C @Offset 0000BE7Ch in File:pro.exe"
L’offset représente l’emplacement du "JE" et donc retenez le numéro qui est entre les "0" et le "h" qui signifie que c’est en hexadécimal. Donc ici : BE7C.

Allez dans votre éditeur hexadécimal, ouvrez le fichier et pour "HexDecCharEditor" faîtes "Edit" à "Go to…" ou bien CTRL + T. Tapez dans la case hexadécimal "hex" BE7C et faîtes "Ok". Vous arriverez directement sur notre 74 que cous changerez en EB.

Une fois ce changement effectué, il faut voir si ça marche. Donc on lance le fichier modifié en lui mettant un autre nom que "pro.exe". Et là il nous met un nouveau message nous disant que le fichier a été altéré, peut-être par un virus… Ben forcément qu’il a été altéré, on vient de le modifier. Donc on recherche ce message dans WinDasm.

Vous pouvez dans ce dernier, pour chercher une phrase, faire "Search" puis "Find" et taper un morceau de la phrase comme par exemple "been altered" ou "by a virus". Faîtes attention qu’il n’y en ait pas plusieurs. Et évitez de prendre les fins des phrases car elles sont souvent coupées.

:0040BECD 741C je 0040BEEB
:0040BECF A144D74700 mov eax, dword ptr [0047D744]
:0040BED4 3B30 cmp esi, dword ptr [eax]
:0040BED6 7413 je 0040BEEB
:0040BED8 53 push ebx
:0040BED9 53 push ebx
* Possible StringData Ref from Data Obj ->"This program has been altered, "

On peut voir deux "JE" avant le message se reportant au même endroit. Vous pouvez donc prendre celui que vous voulez et le remplacez par un JMP (EB en hexadécimal pour ceux qui ne le sauraient pas encore).

Donc là on est enfin prêt.

Faîtes maintenant "Help" puis "Register…". On voit que le logiciel est limité à 40 utilisations. Bon alors on fait comme d’habitude : nom, société et le code (bidon bien sûr). On clique sur "Ok" et là …[roulements de tambour]… un message d’erreur qui dit que le code n’est pas bon. Ben oui, vous voulez que ça soit quoi d’autre ?

On fait une petite recherche dans WinDasm. Si vous avez du mal à trouver dans les Data String References (dans le haut de la liste) faîtes donc une recherche de texte (voir plus haut) avec "registration number" par exemple. Faîtes attention à ne pas faire de fautes en recopiant. Bon et donc on aperçoit notre beau message :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00427CBE(C)
|
:00427CF1 8945F0 mov dword ptr [ebp-10], eax
* Possible Reference to String Resource ID=07033: "We're sorry! The registration number you entered appears to"

Ce message est appelé par un saut conditionnel en 00427CBE, donc on se dirige vers cette adresse et on voit ceci :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00427C78(C)
|
:00427CB0 57 push edi
:00427CB1 E80F090000 call 004285C5
:00427CB6 85C0 test eax, eax
:00427CB8 A1ECF44700 mov eax, dword ptr [0047F4EC]
:00427CBD 59 pop ecx
:00427CBE 7531 jne 00427CF1
:00427CC0 8945F0 mov dword ptr [ebp-10], eax
* Possible Reference to String Resource ID=07032: "You haven't entered a valid username. Your username must be"

On tombe sur un "JNE" et on voit qu’il fait un test avant, donc on aurait tendance à dire qu’il faudrait le modifier (cette manie de modifier tous les sauts maintenant...). Mais regardez en dessous : "You haven’t entered a valid name". Donc si on modifie ce saut on tombera sur ce message. On regarde alors encore au dessus du saut et on qu’il est appelé par un autre saut conditionnel. On y va :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00427C33(C)
|
:00427C6F 8BBFD5000000 mov edi, dword ptr [edi+000000D5]
:00427C75 395FF8 cmp dword ptr [edi-08], ebx
:00427C78 7536 jne 00427CB0
:00427C7A A1ECF44700 mov eax, dword ptr [0047F4EC]
:00427C7F 8945F0 mov dword ptr [ebp-10], eax
* Possible Reference to String Resource ID=07031: "You must enter your username in the Name field, exactly as y"

Là on se retrouve dans le même cas. Si on modifie ce saut on tombera sur le message d’erreur qui se trouve en dessous du "JNE". Ce saut est appelé par un autre saut. On n’en finit plus.

On arrive donc en 00427C33 :

:00427C2F 3945E8 cmp dword ptr [ebp-18], eax
:00427C32 59 pop ecx
:00427C33 753A jne 00427C6F
:00427C35 A1ECF44700 mov eax, dword ptr [0047F4EC]
:00427C3A 8945F0 mov dword ptr [ebp-10], eax
* Possible Reference to String Resource ID=07026: "Thank you! Your copy of Teleport Pro is now registered. Al"

Et là que voit-on ? Un beau message nous disant qu’on est enregistré. Youpi, youpi ! On va donc nopper ce saut en remplaçant 753A par 9090 pour qu’il tombe toujours sur ce message. On modifie et bien sûr on essaye. Et là ça marche enfin !

Relancez le programme et regardez dans "Help" puis "About". Vous n’êtes plus enregistré ! Il suffit juste de retaper le code à chaque fois que vous lancez le logiciel. Mais bon… On va plutôt s’intéresser à la fenêtre d’About où l’on peut voir "This copy of Teleport Pro is UNREGISTERED". On va donc chercher ce dernier mot dans WinDasm. On tombe ici :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00416D9E(C)
|
:00416DD0 3898CD040000 cmp byte ptr [eax+000004CD], bl
:00416DD6 7430 je 00416E08
:00416DD8 894DF0 mov dword ptr [ebp-10], ecx
* Possible Reference to String Resource ID=07093: "This copy of Teleport Pro is UNREGISTERED."

Et un peu plus bas :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00416DD6(C)
|
:00416E08 8B900D050000 mov edx, dword ptr [eax+0000050D]
:00416E0E 395AF8 cmp dword ptr [edx-08], ebx
:00416E11 7537 jne 00416E4A
:00416E13 8BB809050000 mov edi, dword ptr [eax+00000509]
:00416E19 894DF0 mov dword ptr [ebp-10], ecx
* Possible Reference to String Resource ID=07102: "Licensed to %s"

Donc on va modifier le "JE" (en 00416DD6) en JMP (EB) pour arriver tout le temps sur "Licensed to…". Mais si on regarde au dessus du message de "UNREGISTERED". Il y a :

:00416D9E 7430 je 00416DD0
:00416DA0 894DF0 mov dword ptr [ebp-10], ecx
* Possible Reference to String Resource ID=07076: "The free evaluation period for Teleport Pro has expired."

Ça c’est le message qu’il nous mettra passé les 40 utilisations. Donc on met un JMP à la place du JE, qui arrivera sur le JMP du 00416DD6 qui sautera lui à son tour vers "Licensed…". Et voilà !


Cours n°9 : Cracker un prog compressé

Cours n°9

Cracker un prog compressé

Objectif :

Cracker et trouver le serial d'un programme qui a été compressé

Niveau :

Assez facile (ou alors c'est que j'explique mal)

Ce qu'il vous faut :

- WinDasm + son patch

- Un éditeur hexadécimal

- Le crack-me (130 Ko)

- Le logiciel pour le décompresser : UPX (120 Ko)

Bon et bien allons-y gaiement !

Pour ce cours je vais utiliser un WinDasm patché, car avec les autres versions on ne verra pas les Strings Data References. Ne me demandez pas pourquoi, je n'en sais rien. Je vous conseille de garder votre ancienne version de WinDasm + la version patchée. Car ils ne donnent pas les mêmes résultats pour certains programmes. Ce patch vous permettra également de voir les Data String References des logiciels programmés en Visual Basic et il met des couleurs (comme c'est joli :-).

Bon allez je crois que tout est prêt, alors on peut y aller. Tout d'abord comme d'habitude, en première phase, l'observation. On lance donc ce crack-me qui a été programmé en Delphi (on peut connaître le langage de programmation ou le type de compression avec des logiciels comme StudPe ou Peid par exemple). On voit une fenêtre dans laquelle, il faut rentrer le bon serial. On tape un joli serial bidon, du style 654321 pour varier un peu :-). On valide et là : "Wrong Serial - You are a bad cracker". Pfffff. "You are a bad cracker" : c'est vite dit ça. Attends un peu que je te désassemble, tu feras moins le malin après. Non mais ! Donc une fois cette brève partie "sketch" de ce cours passée on lance notre WinDasm patché et on désassemble ce crack-me. Et qu'est-ce qu'on voit aux toutes premières lignes :

Object01: UPX0 RVA: 00001000 Offset: 00000400 Size: 00000000 Flags: E0000080
Object02: UPX1 RVA: 00036000 Offset: 00000400 Size: 0001FA00 Flags: E0000040
Object03: .rsrc RVA: 00056000 Offset: 0001FE00 Size: 00001200 Flags: C0000040

On peut voir qu'il a été compressé avec UPX. Et comme il est compressé, on ne voit pas de Data Strings References. C'est là qu'intervient à la rescousse notre décompresseur UPX (si vous n'avez pas de décompresseur il est toujours possible de le faire manuellement , pour cela regardez les tuts à ce sujet rubrique "Autres Cours"). Pour le décompresser il faut copier le crack-me dans le même dossier que le décompresseur et renommez le fichier à décompressé en "1.exe" (ou alors modifier le 'decompress.bat'). Une fois cette modification effectuée, on lance le 'decompress.bat'. Et voilà le prog est décompressé.

On désassemble alors le crack-me décompressé. Et là on voit enfin les Data Strings References. On cherche notre message "You are a bad cracker". Il n'y en a qu'un seul, donc pas de problème :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00442B8B(C)
|
:00442BA7 6A00 push 00000000

* Possible StringData Ref from Code Obj ->"Wrong Serial"
|
:00442BA9 B9582C4400 mov ecx, 00442C58

* Possible StringData Ref from Code Obj ->"You are a bad cracker!"

On va donc à l'adresse 00442B8B qui est juste au dessus :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00442B5D(C)
|
:00442B79 8D55FC lea edx, dword ptr [ebp-04]
:00442B7C 8B83C4020000 mov eax, dword ptr [ebx+000002C4]
:00442B82 E835FCFDFF call 004227BC
:00442B87 837DFC00 cmp dword ptr [ebp-04], 00000000
:00442B8B 751A jne 00442BA7
:00442B8D 6A00 push 00000000

* Possible StringData Ref from Code Obj ->"Nothing entered"
|
:00442B8F B92C2C4400 mov ecx, 00442C2C

* Possible StringData Ref from Code Obj ->"You have to enter a serial"

Mais on voit que si on change ce saut, il va nous dire "Nothing entered" (= "rien d'entré" pour ceux qui ne sont pas bilingues). Ce message est appelé par un autre saut conditionnel, donc on y va :

* Possible StringData Ref from Code Obj ->"12011982"
|
:00442B53 BAE82B4400 mov edx, 00442BE8

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00442AE7(C)
|
:00442B58 E8E70FFCFF call 00403B44
:00442B5D 751A jne 00442B79
:00442B5F 6A00 push 00000000

* Possible StringData Ref from Code Obj ->"Trial CrackMe Cracked!"
|
:00442B61 B9F42B4400 mov ecx, 00442BF4

* Possible StringData Ref from Code Obj ->"Congrats! You were successful!"

Ah ben ce message me plait bien ! On va donc forcer le programme à arriver sur ce message. On changera donc le 751A en 741A ou 9090.

Un petit point sur l'enregistrement car je reçois également beaucoup de mails à ce sujet et j'en ai marre de répéter à chaque fois la même chose. Quand vous enregistrez votre programme modifié dans l'éditeur hexadécimal, si vous voulez enregistrer par dessus l'ancien il faudra fermer le crack-me s'il est déjà lancé et fermer WinDasm s'il est désassemblé avec. Sinon il vous dira "Writing failed". Mais le mieux est de l'enregistrer sous un autre nom. Et n'oubliez pas de mettre le ".exe" à la suite du nom quand vous enregistrez. Voilà c'était juste un rappel !

Bon le crack-me est désormais cracké. Mais pour trouver le bon serial retournons un peu en arrière :

* Possible StringData Ref from Code Obj ->"12011982" <-- Tiens c'est bizarre ça !!!!
|
:00442B53 BAE82B4400 mov edx, 00442BE8

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00442AE7(C)
|
:00442B58 E8E70FFCFF call 00403B44
:00442B5D 751A jne 00442B79
:00442B5F 6A00 push 00000000

* Possible StringData Ref from Code Obj ->"Trial CrackMe Cracked!"
|
:00442B61 B9F42B4400 mov ecx, 00442BF4

* Possible StringData Ref from Code Obj ->"Congrats! You were successful!"

Avez-vous remarqué ce nombre : 12011982. Et bien c'est notre serial. Pas trop dur à trouver cette fois-ci, hein ?

Donc voilà comment cracker un programme qui semblait incrackable car il n'y avait pas de Data Strings References.


Cours n°10 : Cracker un crackme et trouver le serial

Cours n°10

Cracker un crackme et trouver le serial

Objectif :

Faire un peu tout et n'importe quoi du moment qu'on arrive à obtenir le bon message et le bon serial. ;-)

Niveau :

Je n'en sais rien ! Ça dépend de vous. ;-) Mais plus compliqué que les précédents.

Ce qu'il vous faut :

- WinDasm

- Un éditeur hexadécimal

- Le crack-me (8 Ko)

Aller on y va !

Je ne sais pas trop par quoi commencer mais enfin bon, on y va quand même ! Alors déjà après l'avoir téléchargé, curieux comme vous êtes, vous avez dû lancer ce petit crackme. Et vous avez dû voir qu'il n'y a pas de case pour le nom ! Donc soit le serial est unique, soit il est calculé avec par exemple le numéro du disque dur ou soit avec l'âge de votre belle-mère. A première vue ça m'a l'air d'être un serial unique.

Ni une, ni deux, on ouvre WinDasm et on désassemble ce crackme. On recherche dans les Data Strings Reference le vilain message d'erreur : "Bzzz, Essaye encore!!". Et on arrive sur ce message :

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00401429(C), :00401436(C), :0040143F(C), :00401448(C) --> pour ceux qui n'auraient par encore compris ou fait attention le (C) veut dire saut Conditionnel et le (U) saut inconditionnel (Unconditional)
|
:00401465 FC cld

* Possible StringData Ref from Data Obj ->"Bzzz, Essaye encore!!"
|
:00401466 6838314000 push 00403138
:0040146B FF3599314000 push dword ptr [00403199]

Ce message est donc appelé par 4 sauts conditionnels. Ce qui est bien c'est qui sont tous rassemblés un peu plus haut ce qui évite contrairement à certains programmes de retrouver les sauts éparpillés au quatre coins du code.

:00401418 E88B000000 Call 004014A8
:0040141D A37D314000 mov dword ptr [0040317D], eax
:00401422 833D7D31400003 cmp dword ptr [0040317D], 00000003
:00401429 753A jne 00401465
:0040142B BBF0314000 mov ebx, 004031F0
:00401430 0FBE03 movsx eax, byte ptr [ebx]
:00401433 83F84D cmp eax, 0000004D
:00401436 752D jne 00401465
:00401438 43 inc ebx
:00401439 0FBE03 movsx eax, byte ptr [ebx]
:0040143C 83F84F cmp eax, 0000004F
:0040143F 7524 jne 00401465
:00401441 43 inc ebx
:00401442 0FBE03 movsx eax, byte ptr [ebx]
:00401445 83F849 cmp eax, 00000049
:00401448 751B jne 00401465
:0040144A EB00 jmp 0040144C

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040144A(U)
|
:0040144C FC cld

* Possible StringData Ref from Data Obj ->"Arf C'est le bon Code!!"

Donc ces 4 sauts nous amenant sur le message d'erreur il faut les remplacer. Des NOP partout doivent très bien marcher (bonjour l'élégance du crack me direz-vous) ! Mais au moins ça a le mérite de marcher ! ;-)

Analyse du code à l'aide du débuggeur

Afin de trouver le bon code on va donc utiliser le débuggeur de WinDasm. Donc CTRL+L ou Debug --> Load Process et là, tel Sésame, le débuggeur s'ouvre. Il faudrait poser un BP (Break Point) avant ces 4 sauts. Donc on met un BP avant l'arrivée du premier saut qui nous amène sur le message d'erreur. Comme le CALL n'est pas intéressant on va poser le BP sur la ligne d'après.

:00401418 E88B000000 Call 004014A8
:0040141D A37D314000 mov dword ptr [0040317D], eax --> Break Point sur cette ligne
:00401422 833D7D31400003 cmp dword ptr [0040317D], 00000003
:00401429 753A jne 00401465

Inutile de cocher les 5 cases de la fenêtre de droite, car là on n'en aura pas besoin. On fait Run (F9) et on tape un code bidon : 123456. On valide et là il break. Là il va falloir s'occuper de la fenêtre de gauche. Mais tout d'abord on analyse un peu le code.

Je ferai, dans un prochain cours, un cours sur l'assembleur pour que vous ayez quelques bases car cela vous servira plus d'une fois.

:0040141D A37D314000 mov dword ptr [0040317D], eax --> Vous êtes ici ! :-) La valeur de eax est copié à l'adresse 0040317D et la température extérieur est de 23.6°C ! (oups pardon !)
:00401422 833D7D31400003 cmp dword ptr [0040317D], 00000003 --> la valeur en 0040317D (donc eax) est comparé à 3 (3 en hexa = 3 en decimal)

On va donc regarder ce qu'il y a dans EAX pour voir ce qu'il compare à 3, car si EAX n'est pas égal à 3 (JNE) il saute vers le mauvais message. Pour cela il faut regarder dans le cadre "regs" en haut à gauche de la fenêtre de gauche. On voit EAX = 00000006. Notre code était 123456 donc la valeur de EAX doit être le nombre de lettres. On peut essayer avec d'autres serials bidons pour vérifier. Donc le vrai serial fait 3 caractères. Il faut donc recommencer le débuggage pour rentrer cette fois un serial de 3 caractères. Le BP toujours sur la ligne du "mov", on rentre cette fois 123, on valide. Et là il break. Là on va y aller en Pas à Pas (Step) dans les boutons à coté de Run. Le Step Into (F7) est le pas à pas en rentrant dans les CALL et Step Over (F8) sans rentrer dedans en passant par dessus (enfin le programme rentre bien sur dedans mais vous ne le voyez pas). Là prenez celui que vous voulez car on n'a pas de CALL à traverser.

Les modifications des registres s'effectuent une fois la ligne passée. Si par exemple vous êtes sur une ligne avec 'mov eax, 6', eax sera égale à 6 seulement quand vous serez passé à la ligne suivante.

Donc on avance pas à pas jusqu'à la ligne du premier JNE. Ayant rentré un serial de 3 chiffres, il ne devrait pas sauter. On appuie sur F7 ou F8 pour passer cette ligne : Ouf ! il ne saute pas ! Là on est sur une ligne où il y a : mov ebx, 004031F0 . Il va donc copier une valeur dans ebx. On passe cette ligne pour regarder ce que contient ebx. Voici un exemple avec la valeur de eax et le contenu de ecx.

Donc il faut cliquer sur "ebx" pour voir ce qu'il y a mis. Et là on voit le code '123' que nous avons tapé. Le serial bidon est donc stocké dans ebx.

Ensuite il y a cette ligne : movsx eax, byte ptr [ebx] qui va donc donner la valeur d'un byte (en anglais bit=bit et byte=octet ! Ne pas confondre ! un octet correspond là à un caractère) de ebx (de notre serial) à eax. En effet on voit ensuite que eax = 31. Et 31 (en hexa bien sur) représente le caractère '1' (en ascii). L'ascii c'est le format qui regroupe les caractères que nous voyons à l'écran (lettres, chiffres, caracteres spéciaux...) mais aussi d'autres tels que le retour à ligne par exemple. Pour connaître les correspondances, allez dans HexDecCharEditor et cliquez sur "characters table" qui est représenté par un rectangle jaune. Regardez à la valeur 31 hexa et vous verrez qu'il correspond au chiffre 1 en Window Char (Ascii).

Après avoir mis le premier caractère dans eax, il le compare à 4D (hexa) : cmp eax, 0000004D. On cherche donc dans la liste des valeurs hexa 4D et on voit qu'il correspond à M. Ensuite il incrémente (augmenter de 1) ebx (inc ebx) pour passer à la lettre suivante et compare les deux lettres suivantes par 4F et 49 (O et I). Après chaque test il y a un JNE qui saute si la lettre n'est pas la même.

Donc petit récapitulatif :

:0040141D A37D314000 mov dword ptr [0040317D], eax --> nombre de caractères du serial bidon
:00401422 833D7D31400003 cmp dword ptr [0040317D], 00000003 --> compare ce nombre à 3
:00401429 753A jne 00401465 --> si pas égal saute vers mauvais message
:0040142B BBF0314000 mov ebx, 004031F0 --> mets notre serial bidon dans ebx
:00401430 0FBE03 movsx eax, byte ptr [ebx] --> la valeur de eax est le premier caractère du serial bidon en hexa
:00401433 83F84D cmp eax, 0000004D --> compare la 1ere lettre à 4D (M)
:00401436 752D jne 00401465 --> saute si pas égal
:00401438 43 inc ebx --> ebx + 1 : caractère suivant
:00401439 0FBE03 movsx eax, byte ptr [ebx] --> la valeur de eax est le deuxième caractère du serial bidon en hexa
:0040143C 83F84F cmp eax, 0000004F --> compare le 2eme caractère à 4F (O)
:0040143F 7524 jne 00401465 --> saute si pas égal
:00401441 43 inc ebx --> ebx + 1 : caractère suivant
:00401442 0FBE03 movsx eax, byte ptr [ebx] --> la valeur de eax est le troisième caractère du serial bidon en hexa
:00401445 83F849 cmp eax, 00000049 --> compare le 3ème caractère à 49 (I)
:00401448 751B jne 00401465 --> saute si pas égal
:0040144A EB00 jmp 0040144C --> saute vers bon message

Le bon serial est donc MOI.

Pour obtenir le bon message avec un serial faux vous pouvez aussi inverser les PUSH :

* Possible StringData Ref from Data Obj ->"Arf C'est le bon Code!!"
|
:0040144D 6820314000 push 00403120
:00401452 FF3599314000 push dword ptr [00403199]

* Possible StringData Ref from Data Obj ->"Bzzz, Essaye encore!!"
|
:00401466 6838314000 push 00403138 --> A remplacer par push 00403120
:0040146B FF3599314000 push dword ptr [00403199]

Le push après le mauvais message devra être remplacé par celui du bon message. Donc il faut mettre push 00403120 (6820314000) à la place de push 00403138 (6838314000). Pour info : PUSH = 68, et ensuite il faut lire à l'envers 2 par 2 : 6820314000 = push 00403120

Et voilà encore un crack-me de cracké et avec la manière ! ;-)


Cours n°11 : Le Crackme qui se cracke tout seul

Cours n°11

Le Crackme qui se cracke tout seul

Objectif :

1° Faire en sorte que le crakme se patche lui-même.

2° Cracker le crackme pour qu'il nous donne le bon serial.

Niveau :

Avancé. (Non mais bougez pas, restez sur votre siège !)

Ce qu'il vous faut :

- Connaître les bases de l'Assembleur (lire mon cours à ce sujet)

- Savoir utiliser WinDasm (si vous avez compris mes cours précédents c'est bon ! ;-) )

- WinDasm

- Un éditeur hexadécimal

- Le crack-me du cours n°3 et 4 (il aura souffert celui là !)

- StudPE (409 Ko)

Introduction

Je n'ai pas de définition exacte du Reverse Engineering, donc je ne sais pas si on peut considérer ce cours comme étant un cours de Reverse Engineering. Mais cela n'est pas bien grave. Pour ceux qui ne savent pas ce que c'est, c'est l'art de modifier ou d'ajouter des fonctions d'un programme en rajoutant, modifiant du code de ce dernier. Enfin du moins c'est comme ça que je le définirais.

Dans ce cours tout ce que je vais vous montrer ne vous servira pas forcément plus tard. Je fais cela juste pour m'amuser, pour utiliser mes connaissances en assembleur et pour vous en faire profiter. Donc je vous montrerai des méthodes qui sont beaucoup plus longues que la simple inversion de saut, mais dont le résultat revient au même.

Dans ce cours vous apprendrez également à mieux utiliser le débuggeur de WinDasm.

Le Crack-Me qui se patche lui-même !

J'ai pris le crackme du cours n°3 et 4. Donc nous l'avons déjà cracké ensemble de manière brève mais efficace. Le crack-me comporte juste une boite de dialogue qui demande un serial. Et si c'est pas le bon, comme d'habitude, nous avons le droit à un beau message "Incorrect try again!!".

Maintenant que vous avez téléchargé StudPe, cela va (ou plutôt doit) devenir un réflexe désormais : regarder si le programme est compressé ou voir en quel langage il a été programmé. Donc lancez StudPe, allez directement à Options, ne passez pas par la case départ et ne recevez pas 20.000 F. Oups pardon je m'égare quelque peu ! Une fois sur l'onglet "Options", cochez la case "Shell Menu Extension". Cela vous permettra, en faisant un clic droit sur un .exe ou .dll de l'analyser avec StudPe grâce à l'ajout d'un raccourci dans le menu "clic-droit". N'oubliez pas de configurer l'éditeur hexa avec la case "HexEditor" en sélectionnant le .exe de votre éditeur préféré.

Une fois tout cela fait, ouvrez notre crackme avec et allez à l'onglet "Signature". Là on peut voir qu'il a été programmé avec Visual C++ 6.0. Donc il n'est pas compressé. Bon allez, vous vous amuserez à voir les autres onglets quand j'aurai fini mon cours.

Bon cela ne nous empêche pas, bien sûr, de désassembler notre cher crackme. On recherche le message d'erreur, on tombe sur :

:0040158D FF1500204000 Call dword ptr [00402000]
:00401593 85C0 test eax, eax
:00401595 7516 jne 004015AD
:00401597 6A40 push 00000040

* Possible StringData Ref from Data Obj ->"CrackMe"
|
:00401599 6850304000 push 00403050

* Possible StringData Ref from Data Obj ->"Correct way to go!!"
|
:0040159E 6858304000 push 00403058
:004015A3 8B4DE0 mov ecx, dword ptr [ebp-20]

* Reference To: MFC42.Ordinal:1080, Ord:1080h
|
:004015A6 E853050000 Call 00401AFE
:004015AB EB14 jmp 004015C1

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401595(C)
|
:004015AD 6A40 push 00000040

* Possible StringData Ref from Data Obj ->"CrackMe"
|
:004015AF 686C304000 push 0040306C

* Possible StringData Ref from Data Obj ->"Incorrect try again!!"

Oula que c'est dur à cracker ça ! ;-) Bon il faut donc modifier le JNE en 00401595 et le remplacer par un JE ou deux NOP. Il va falloir que le crackme modifie lui-même ce saut comme un grand :-). Pour modifier par exemple le JNE en JE on mettra donc :

MOV byte PTR [00401595], 74 : il s'agit donc de mettre 74 (JE) à l'adresse 401595.

Il va falloir insérer cette instruction à un endroit où il y a de la place (là où c'est plein de 00). La plupart du temps vers la fin du programme on trouve de la place. Ouvrez le avec l'éditeur hexadécimal et allez donc vers la fin du programme. On le mettra par exemple à l'offset 4890. Et pour que le programme passe par ici il va falloir l'ammener par un JMP. Donc il faut trouver une instruction qu'on peut remplacer par le JMP. Et il ne faut pas oublier non plus le JMP après le MOV pour pouvoir revenir de là où on vient. J'espère avoir été assez clair.

Petit rappel : pour un saut court le premier byte (octet) correspond au type de saut et le deuxième au nombre (en hexa bien sur) de bytes qu'il va sauter. Ex: 7516 : 75 = JNE et saute de 16 bytes.

Le problème ici c'est que le saut est largement supérieur à FFh (255d) octets. Il prendra environ 5 bytes (1 pour le 74 et 4 pour la taille du saut) ce qui est donc impossible à faire comme cela. Si le saut avez été inférieur à FF on aurait très bien pu faire comme ceci : exemple :

  • JMP 00401234 --> le programme original saute à 00401234

  • xxxxxxx --> instructions

  • [...]

  • xxxxxxx

Remplacer par :

  • JMP 00404890 --> saut à l'endroit que nous avons créé

  • xxxxxxx --> instructions

  • [...]

  • xxxxxxx

  • MOV byte PTR [00401595], 74 --> modifie le JNE en JE (75 par 74)

  • JMP 00401234 --> saute à l'endroit prévu par le programme original

Mais là il va falloir remplacer une instruction qui ne sert pas (ou plutôt qui sert mais à rien ;-) ) d'une taille minimum de 5 bytes et dont le programme passe forcément dessus.

Donc on regarde au-dessus du test qui ammène de notre message d'erreur car il y passe forcément :

* Reference To: KERNEL32.lstrlenA, Ord:0308h
|
:00401560 FF1504204000 Call dword ptr [00402004]
:00401566 8945F0 mov dword ptr [ebp-10], eax --> met le nombre de lettres du serial dans ebp-10
:00401569 837DF001 cmp dword ptr [ebp-10], 00000001 --> compare à 1
:0040156D 7316 jnb 00401585 --> si c'est supérieur saute, si pas de lettre ne saute pas
:0040156F 6A40 push 00000040

* Possible StringData Ref from Data Obj ->"CrackMe"
|
:00401571 682C304000 push 0040302C

* Possible StringData Ref from Data Obj ->"Enter Registration Number"
|
:00401576 6834304000 push 00403034
:0040157B 8B4DE0 mov ecx, dword ptr [ebp-20]

* Reference To: MFC42.Ordinal:1080, Ord:1080h
|
:0040157E E87B050000 Call 00401AFE
:00401583 EB3C jmp 004015C1

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040156D(C)
|
:00401585 8D4DE4 lea ecx, dword ptr [ebp-1C]
:00401588 51 push ecx
:00401589 8D55F4 lea edx, dword ptr [ebp-0C]
:0040158C 52 push edx

* Reference To: KERNEL32.lstrcmpA, Ord:02FCh
|
:0040158D FF1500204000 Call dword ptr [00402000]
:00401593 85C0 test eax, eax
:00401595 7516 jne 004015AD --> saute vers bon ou mauvais message
:00401597 6A40 push 00000040

* Possible StringData Ref from Data Obj ->"CrackMe"
|
:00401599 6850304000 push 00403050

* Possible StringData Ref from Data Obj ->"Correct way to go!!"

Là où il teste s'il y a des caractères ou non, il y aurait de la place car ce test ne sert à rien pour nous, car il nous force à taper quelque chose. Et faignant comme on est (je sais pas vous mais moi si :-) ) et bien pouvoir ne rien taper c'est mieux.

Vous vous demandez peut-être comment je sais que c'est le nombre de caractères du serial. Et bien lancez le débuggeur, mettez un BreakPoint sur la ligne en 00401566, faites "Run", tapez n'importe quoi comme serial et faites Ok. Là il break ! Regardez dans la fenêtre des registres (celle en bas à gauche) et plus particulièrement le cadre "Regs". Vous pouvez voir la valeur de EAX. En mettant "123456" comme serial vous aurez 6.

Bon et bien pendant qu'on break ici et bien on va installer notre patch ! On clique sur "Patch Mode" de la fenêtre de droite. Et là on va pouvoir taper directement en assembleur nos instructions. Donc on tape : JMP 00404890. C'est là qu'on va placer le patch en lui-même. On fait Entrée, si l'instruction est fausse il l'indique et si c'est juste il la met dans le cadre du dessous. L'avantage de le faire ici c'est de pouvoir voir la correspondance en hexa, qu'on pourra éventuellement recopier dans l'éditeur hexadécimal.

Une fois l'instruction validée, on fait "Apply Patch", là une fenêtre de confirmation s'affiche. Si vous ne la voyez pas c'est qu'elle est cachée derrière la fenêtre actuelle. On fait "Oui" et avant de fermer la fenêtre du Patch Mode on clique sur "Copy" et on colle ça quelque part pour s'en souvenir. Les modifications effectuées ne se voient pas dans la fenêtre principale de WinDasm mais dans celle de droite.

Là on va devoir aller à 00404890 donc on fait "F7" - Step Into ou "F8" - Step Over (peu importe). Les deux permettent d'avancer en pas à pas. Mais sachez que le Step Into rentre dans les CALL contrairement au Step Over.

Une fois le F7 (ou F8) fait, on voit : :00404890 add byte ptr [eax], al (toujours dans la fenêtre de droite). C'est là où le JMP nous a emmené. On va insérer notre patch ici même, donc on clique sur "Patch Mode" et on met :

  • MOV byte PTR [00401595], 74
  • JMP 00401585

    Pourquoi sauter à 00401585 et pas sauter à l'élastique ? Rappelez-vous ! On avait : :0040156D 7316 jnb 00401585 --> si c'est supérieur saute, si pas de lettre ne saute pas.

    Donc il saute à cette adresse s'il y a des caractères. Donc là on pourra mettre rien du tout et il nous dira quand même que c'est bon. C'est-y pas magique ? Ben en fait non ! Car appliquez ce patch là (n'oubliez pas de copier les instructions) ! Pour le tester ensuite, cliquez sur Run. Et là plantage ! AAARRRRGGG ! Cela vient du fait que la section dans laquelle doit être effectuée le patch ne peut pas être modifiée (comme si elle était en lecture seule).
    Faites quand même les modifications que l'on vient d'effectuer dans l'éditeur hexadécimal. Si vous n'avez pas copié les instructions en hexa vous n'avez plus qu'à recommencer ! Et respectez bien les endroits !!!

    Ouvrez ensuite le crackme modifié avec StudPe. Allez dans "section", double-cliquez sur la ligne de ".text" et cochez "MEM_WRITE". Faites Save et voilà, vous pouvez maintenant le tester : notre crackme est cracké en s'auto-patchant.

    Pour remplacer le JNE par des NOP il aurait fallu inscrire :

    :00401DE0 66C6059515400090 mov byte ptr [00401595], 90
    :00401DE8 66C6059615400090 mov byte ptr [00401596], 90

    Petite variante

    On aurait pu mettre un CALL à la place d'un jump. Exemple :

    Voici ce qu'il y a dans le programme original :

    :00401566 8945F0 mov dword ptr [ebp-10], eax --> met le nombre de lettres du serial dans ebp-10
    :00401569 837DF001 cmp dword ptr [ebp-10], 00000001     --> compare à 1
    :0040156D 7316 jnb 00401585     --> si c'est supérieur saute, si pas de lettre ne saute pas

    Si on break sur la ligne en 00401566 on peut voir que EBX est tout le temps égal à 1. Donc on pourrait mettre :

    :00401566 39D8 cmp eax, ebx --> Compare notre nombre de lettres à EBX (=1), ce qui revient au même que les 2 lignes MOV et CMP.
    :00401568 E823330000 call 00404890 --> Va à l'endroit de notre patch

    Puis à l'endroit du patch, en 00404890 :

    :00404890 66C6059515400074 mov byte ptr [00401595], 74 --> Remplace le saut 75 en 74
    :00404898 C3 ret --> Retourne juste après le CALL

    Par contre si vous ne mettez rien comme serial il vous demandera d'en mettre un.

    Le crackme qui nous donne le bon serial !

    Voilà comment le crackme vous donne le bon serial !

    * Possible StringData Ref from Data Obj ->"CrackMe"
    |
    :004015AF 686C304000     push 0040306C

    * Possible StringData Ref from Data Obj ->"    Incorrect try again!!"
    |
    :004015B4 6874304000     push 00403074
    :004015B9 8B4DE0 mov ecx, dword ptr [ebp-20]

    Le PUSH envoie en fait le texte situé au dessus que WinDasm nous montre gentiment :-). Pour le vérifier lancez le debuggeur, et tapez le numéro qui suit le PUSH dans la case "User Addr" 1 ou 2 (en dessous des registres) et cliquez en dessous sur UA1 ou UA2 selon le numéro choisi. En mettant 00403074 vous pourrez voir "Incorrect try again". Les autres messages se situent vers le même endroit (dans les 004030xx), on va donc aller dans l'éditeur hexa à l'offset 3074 par exemple. Et là on voit les autres messages dont le serial !! On repère donc son adresse en relevant l'offset du premier caractère ('<') du serial. Ici on a 3020. Donc on va remplacer le PUSH que vous souhaitez par un PUSH 00403020.

    Par exemple celui du "Incorrect..." : 6874304000 push 00403074
    On mettra à la place : 6820304000 push 00403020

    Je rappelle la correspondance du PUSH en hexa : PUSH = 68 et ensuite les octets sont inversés, regardez le joli (si si regardez bien ! :-P) exemple en couleur :

    6874304000 push 00403074

    Et donc une fois les modifications effectuées, on teste : on met n'importe quoi, on valide ! Et là un beau message vient nous rappeler le bon serial : <BrD-SoB>. Et n'oubliez pas de mettre les < et > pour le bon serial.

    Voilà c'est fini pour aujourd'hui, j'espère vous avoir appris quelque chose de plus avec ce cours ! Et comme toujours si vous avez un problème ou si vous n'avez pas compris quelque chose 2 adresses sont là pour ça : http://www.forumcrack.new.fr/ et deamon.crack (at) netcourrier.com.

    Deamon, le 24 et 25 juillet 2003


Cours n°12 : Reverse Engineering sur la calculatrice Windows

Cours n°12

Reverse Engineering sur la calculatrice Windows

Objectif :

1° Rajouter un menu.

2° Afficher une boîte de dialogue quand on clique sur notre nouveau menu.

Niveau :

Elevé

Ce qu'il vous faut :

- Connaître les bases de l'Assembleur (lire mon cours à ce sujet)

- WinDasm

- Un éditeur hexadécimal

- Un éditeur de ressources (Restorator ou ResHacker)

- La calculatrice de Windows XP (dans Windows/System32/Calc.exe)

- Et un brin de logique !

Introduction

Cette fois-ci ce n'est pas vraiment du cracking mais du Reverse Engineering ! Domaine dans lequel je suis encore débutant mais qui est tellement passionnant et intéressant ! Car ce qu'on va faire ne sert pas à grand chose sinon de se faire plaisir ! :-P Et utilisez nos connaissances.

Pourquoi la calculatrice de Windows particulièrement ? Je ne sais pas trop, j'ai pris le premier petit programme avec un menu que j'ai vu ! Et de plus tout le monde doit l'avoir. Par contre je pense que la calculatrice des Windows précédent au XP ne doit pas être identiques, donc pour faire le cours il faudra vous adapter.

A l'attaque !

Bon alors tout d'abord on va prendre la calculatrice de Windows (dans Windows/System32/Calc.exe) et la copier dans un autre dossier, pour ne pas abîmer l'original car on ne sait jamais !

On va tout d'abord ajouter un menu à coté du menu "?". Pour cela on va utiliser un éditeur de ressources. Ces logiciels sont très utilisés pour la traduction de programmes notamment. Je vais détailler la méthode à utiliser avec 2 éditeurs : Restorator et ResHacker (appelé aussi RessourceHacker ou ResHack) comme ça tout le monde sera content. Personnellement j'utilise Restorator mais le plus utilisé est ResHacker.

Avec Restorator :

Vous lancez donc Restorator, vous faîtes File --> Open et vous sélectionnez la calculette que vous avez copié dans un dossier. Vous pouvez voir différents dossiers sous "Calc.exe". Voici un rapide descriptif de ces dossiers :

  • Menu : comme son nom l'indique ce sont les menus (Fichier, Édition, ...etc)
  • Dialog : les boites de dialogues du logiciel
  • String : les phrases ou mots du logiciel
  • Accelerators : principalement les raccourcis clavier
  • Icon : les icônes du programme
  • Version : les informations sur le programme (numéro de version, auteur...)

    • Donc ce qui nous intéresse ici c'est 'Menu'. On regarde donc dans 'Menu' puis '106'. Là on voit les menus de la calculette (Edition, Affichage et ?). Pour mieux visualiser tout ça cliquez sur le bouton "ab]" (ou Vievew et Edit Mode). Là une fenêtre s'ouvre avec la représentation du menu.

    On va donc ajouter un menu que je nommerai "Infos" et un sous-menu "Reverse" (mais prenez les noms que vous voulez ça n'a pas d'importance). Comme vous pouvez le voir un menu s'écrit comme ceci :

    POPUP "&Edition"
    {
    MENUITEM "Co&pier\tCtrl+C", 300
    MENUITEM "C&oller\tCtrl+V", 301
    }

     Le caractère & signifie que la fonction peut être appelé avec le raccourci clavier : ALT+[lettre qui suit le &]. Ici ALT+E pour Edition.
    Le \tCtrl+C est le raccourci qui exécute la fonction.
    Le 300 ou 301 ici sont les ID des menus. C'est à dire leur numéro pour que le programme puisse les identifier.

    On va donc mettre après le menu "&?" :

    POPUP "&Infos"
    {
    MENUITEM "&Reverse", 318     --> Je mets 318 car il ne semble pas être utilisé par une autre fonction, mais vous pouvez mettre ce que vous voulez du moment qu'il n'est pas déjà pris.
    }

    Une fois cela fait on appuie sur F7 afin de mettre à jour la fenêtre de visualisation. Et on voit bien notre nouveau menu. Ensuite on appuie sur 'Commit changes' (ou F8) pour appliquer les changements. Mais comme vous avez pu le voir il y a plusieurs menus (106 ; 107 ; 108 et 109). Car suivant le mode de la calculatrice (standard ou scientifique) vous n'aurez pas les mêmes menus. Il faut donc rajouter notre menu "Info" - "Reverse" dans les 3 premiers (le 4ème est pour l'aide). L'ID de notre menu doit être le même pour les 3.

    Pensez à faire F8 après chaque modification.

    Voici ce que vous devez obtenir pour le menu 106 :

    106 MENU
    {
    POPUP "&Edition"
    {
    MENUITEM "Co&pier\tCtrl+C", 300
    MENUITEM "C&oller\tCtrl+V", 301
    }
    POPUP "&Affichage"
    {
    MENUITEM "S&tandard", 305
    MENUITEM "S&cientifique", 304
    MENUITEM SEPARATOR
    MENUITEM "Groupement des ch&iffres", 303
    }
    POPUP "&?"
    {
    MENUITEM "&Rubriques d'aide ", 317
    MENUITEM SEPARATOR
    MENUITEM "À &propos de la Calculatrice", 302
    }
    POPUP "&Infos"
    {
    MENUITEM "&Reverse", 318
    }
    }

    Une fois tout cela fait, vous pouvez sauvegarder votre calculette avec le nouveau menu, avec File --> Save As.

    Avec ResHacker :

    C'est quasi-identique ! Vous ouvrez la calc.exe, vous allez dans 'Menu', puis '106' et enfin '1036'. Là il apparaît directement la fenêtre de visualisation du menu. Vous rajoutez :

    POPUP "&Infos"
    {
    MENUITEM "&Reverse", 318
    }

    Et enfin vous cliquez sur 'Enregistrer.' Vous faites la même chose pour le menu '107' et '108'. Et pour finir vous enregistrez le .exe.

    Note :

    Suivant que vous avez modifié avec Restorator ou ResHacker les bytes du programmes ne seront pas identiques, mais je ne pense pas que cela change quelque chose dans la correspondance avec la suite de mon cours. Si toutefois il y a des différences pour les adresses et offsets par la suite, sachez que je vais utiliser la version modifiée avec Restorator.

    Ajout de la MessageBox :

    Comme vous avez pu le constater, notre menu est beau mais pour l'instant il ne sert à rien car lorsqu'on clique dessus rien ne se passe ! Il va donc falloir ajouter l'affichage d'une boite de dialogue. Mais il va falloir que le programme passe par le bout de code qu'on aura rajouté lorsqu'on clique sur le menu. Pour cela on va chercher avec WinDasm l'endroit où le programme analyse quel menu a été cliqué. On va donc désassembler notre calculette modifiée. Et tout au début on peut voir :

    MenuID_006A

    Edition {Popup}
    Copier Ctrl+C [ID=012Ch]
    Coller Ctrl+V [ID=012Dh]

    Affichage {Popup}
    Standard [ID=0131h]
    Scientifique [ID=0130h]
    Groupement des chiffres [ID=012Fh]

    ? {Popup}
    Rubriques d'aide [ID=013Dh]
    À propos de la Calculatrice [ID=012Eh]

    Infos {Popup}
    Reverse [ID=    013Eh] --> Notre menu avec son ID.

    Comme vous l'avez vu l'ID du menu "Reverse" est 13E alors que l'on avait mis 318 ! Et bien comme le "h" l'indique c'est que l'ID est en hexadécimal ici alors que nous l'avions noté en décimal tout à l'heure.

    Pour savoir quel menu a été cliqué le programme va donc comparer la valeur que lui a renvoyé le clic avec les ID des menus. On va donc faire une recherche dans WinDasm de 013D par exemple (l'ID de la rubrique d'aide). On tombe premièrement sur :

    :010026DA 83FE51 cmp esi, 00000051
    :010026DD 7411 je 010026F0
    :010026DF 83FE52 cmp esi, 00000052
    :010026E2 740C je 010026F0
    :010026E4 81FE3D010000     cmp esi, 0000013D
    :010026EA 0F85DF000000 jne 010027CF

    Mais les autres ESI sont comparés à 51 et 52. et en regardant les autres ID, aucun ne correspond à 51 et 52. On continue donc notre recherche. Et on arrive ici :

    * Referenced by a (U)nconditional or (C)onditional Jump at Address:
    |:01003A9F(C)
    |
    :01004166 81FE8D000000 cmp esi, 0000008D
    :0100416C 0F8458010000 je 010042CA
    :01004172 81FE2B010000 cmp esi, 0000012B
    :01004178 0F8667010000 jbe 010042E5
    :0100417E 81FE31010000 cmp esi, 00000131
    :01004184 7620 jbe 010041A6
    :01004186 81FE35010000 cmp esi, 00000135
    :0100418C 0F8653010000 jbe 010042E5
    :01004192 81FE3C010000 cmp esi, 0000013C
    :01004198 7617 jbe 010041B1
    :0100419A 81FE3D010000     cmp esi, 0000013D
    :010041A0 0F853F010000 jne 010042E5

    C'est ici que le programme regarde quel menu a été cliqué. Ce qu'il faudrait qu'on fasse c'est mettre une comparaison de esi avec 13E (notre ID) et faire un saut vers la messagebox si c'est égal. Mais où mettre la comparaison ? Il n'y a pas de place ici ! On voit que cette partie du code est appelée par un saut conditionnel. Et bien c'est parfait ça ! Voici le saut qui mène vers la comparaison des ID :

    :01003A9F 0F87C1060000 ja 01004166

    On va donc remplacer ce JA 01004166 par un JA qui ira vers un endroit où nous allons mettre :

    cmp esi, 13E --> regarde si c'est bien notre menu qui a été cliqué
    jne 01004166 --> si c'est pas le menu saute en 01004166 pour continuer la comparaison
    [afficher la messagebox] --> nous verrons plus tard comment l'écrire
    jmp 01004166 --> retourne en 01004166

    Maintenant il va falloir trouver de la place pour mettre tout ça. On va donc aller voir tout en bas du code dans Windasm. Là où on voit :

    :010136AE 00000000000000000000 BYTE 10 DUP(0)
    :010136B8 00000000000000000000 BYTE 10 DUP(0)
    :010136C2 00000000000000000000 BYTE 10 DUP(0)

    Ce sont des bytes (octets) libres. On aura donc la place de les mettre ici. Regardons cela dans l'éditeur hexadécimal. Effectivement à l'offset 12AAE (adresse 010136AE Windasm) on voit des 00. Pas la peine de trop coller au code qui est au-dessus. On commencera par exemple à écrire en 12AC0 (mais on pourrait commencer n'importe où) donc à l'adresse 010136C0 de WinDasm. On retient cette adresse WinDasm car on va installer notre saut JA. On ouvre donc le débuggeur. Et on pose un break point en 01003A9F (adresse du JA qu'on a décidé de changer). On fait Run et on clique sur la Rubrique d'aide et là bien sur ça break.

    On clique sur Patch Code pour mettre notre nouveau saut : ja 010136C0. On fait entrée et là on clique sur Copy ! Car ce qui nous intéresse c'est le code en hexa. On fait "Apply Patch", on valide et on ferme le Patch Code.

    Avant de continuer je vais vous montrer comment marche une MessageBox :

    MessageBox(
    HWND hWnd,     // Handle de la fenêtre (si 00, pas de fenêtre attribuée)
    LPCTSTR lpText,     // adresse du texte de la MessageBox
    LPCTSTR lpCaption,     // adresse du titre de la MessageBox
    UINT uType     // style de la messagebox (bouton Ok, Annuler...etc) ;

    Donc en assembleur il faut écrire ça à l'envers (car on utilise des PUSH donc à cause de la pile on inverse, cf. cours sur l'assembleur). Ce qui donnera :

    Push code_du_style_souhaité
    Push adresse_du_titre
    Push adresse_du_texte
    Push 00
    Call MessageBox

    Pour le code du style vous pouvez mettre 00 si vous n'avez rien de particulier à mettre (mais on verra ça après).

    Il va falloir écrire le titre et le texte de la message box dans l'éditeur hexa à une adresse que nous aurons définie. On va donc mettre le titre en 12B00 et le texte en 12B20 (adresse windasm : 01013700 et 01013720).

    Il faut maintenant trouver dans le programmes l'adresse du 'Call MessageBox'. Pour cela on va regarder dans les Imports de Windasm. On trouve USER32.MessageBoxW cela devrait faire l'affaire. On double clique dessus plusieurs fois pour voir les Call qui l'appellent. On voit qu'il s'agit de :
    FF15A8110001 Call dword ptr [010011A8]

    Donc maintenant nous avons tous les éléments nécessaires. Nous pouvons donc continuer.

    Toujours dans le débuggeur vous devez être sur le break du JA qui vient d'être modifié. On va donc faire F7 ou F8 (Avance pas à pas). On voit bien dans la fenêtre de droite : :010136C0 add byte ptr [eax], al

    Ce qui signifie que nous sommes bien sur nos octets vides. On lance Patch Code et on va rentrer ces instructions (faites 'entrée' après chaque instruction tapée) :

    cmp esi, 0000013E --> Compare pour savoir si c'est le menu 'Reverse' qui a été cliqué
    jne 01004166 --> Retourne à la routine si c'est pas le menu 'Reverse'
    push 00 --> Style de la MessageBox
    push 01013700 --> Adresse du titre de la MessageBox
    push 01013720 --> Adresse du texte de la MessageBox
    push 00 --> Handle de la fenêtre
    call [010011A8] --> Appel l'API MessageBoxW
    jmp 01004166 --> Retourne à la routine

    N'oubliez surtout pas de faire "copier" et de coller ça dans n'importe quel fichier pour garder les valeurs hexadécimal à rentrer.

    Vous pouvez faire Apply Patch et cliquez sur Run. Là vous cliquez désormais sur "Infos" --> "Reverse"? Il va breaker si vous n'avez pas enlevé le BP et vous recliquez sur Run pour continuer. Et là une boite de dialogue vide apparaîtra. Bon ça à l'air de marcher notre modification ! Il n'y a plus qu'à rentrer tout ça dans l'éditeur hexa :

    :01003A9F 0F871BFC0000 ja 010136C0 --> Offset 2E9F
    --------------------------------------------------
    :010136C0 81FE3E010000 cmp esi, 0000013E --> Offstet 12AC0
    :010136C6 0F859A0AFFFF jne 01004166
    :010136CC 6A00 push 00000000
    :010136CE 6800370101 push 01013700
    :010136D3 6820370101 push 01013720
    :010136D8 6A00 push 00000000
    :010136DA FF15A8110001 call dword ptr [010011A8]
    :010136E0 E9810AFFFF jmp 01004166

    Ensuite vous allez pouvoir marquer le titre que vous souhaitez pour votre messagebox à l'offset : 12B00 (01013700). Mais il faut mettre des 00 entre chaque lettre. Par exemple "I n f o r m a t i o n s". Attention à bien laisser 2 octets ( 2 x "00") avant le 12B20 (adresse du texte). Si votre titre est trop long alors changez l'adresse du texte.

    Une fois le titre écrit on fait pareil pour le message en 12B20. Par exemple "R e v e r s e b y D e a m o n !". Pour séparer 2 mots il faut bien mettre un espace (20 en hexa) en n'oubliant pas les deux "00" qui entourent l'espace "20".

    On enregistre et on teste ! Et voilà ! C'est-y pas beau ? :-) Pour faire mieux on peut changer le style de la messagebox (avec le push 00). Pas besoin de passer par le Patch Code de WinDasm car le PUSH 00 correspond à 6A 00 donc vous pouvez le remplacer directement dans l'éditeur. Attention c'est le premier PUSH 00 (qui se trouve après le jne) car le 2ème c'est la handle de la fenêtre. Voici par quoi remplacer le 00 d'après mes tests ! :-)

    Le premier chiffre correspond au type de message box :

    • 0 : message normal
    • 1 : message d'erreur ( X )
    • 2 : question ( ? )
    • 3 : avertissement ( /!\ )
    • 4 : informations ( i )

    Le second chiffre correspond aux boutons disponibles (Ok, Annuler ...)

    • 0 : Ok
    • 1 : Ok - Annuler
    • 2 : Abandonner - Recommencer - Ignorer
    • 3 : Oui - Non - Annuler
    • 4 : Oui - Non
    • 5 : Recommencer - Annuler
    • 6 : Annuler - Recommencer - Continuer

    Donc en mettant un PUSH 24 vous obtiendrez une question avec Oui et Non comme choix de boutons. Mais sachez qu'en appuyant sur n'importe quel bouton le résultat est le même car nous avons pas fait de test sur le bouton appuyé.

    Voilà ce cours touche à sa fin ! Je viens de vous faire découvrir le reverse engineering, discipline très intéressante. On peut l'utiliser pour par exemple intégrer un keygen à un programme en ajoutant un menu qui nous donnerait le bon serial. Mais il y a pleins d'autres possibilités...

    Deamon, le 22 et 23 août 2003


Cours n°13

Donne le serial au monsieur

Objectif :

Rajouter un bouton au crackme pour que, lorsque l'on clique dessus, un message nous indique quel est le bon serial !

Niveau :

Elevé

Ce qu'il vous faut :

- WinDasm

- Un éditeur hexadécimal

- Un éditeur de ressources (Restorator ou ResHacker)

- Le cracke-me

Introduction

Dans la lignée du cours précédent voici le 13ème cours. Vous allez voir à quoi sert ce que vous avez appris dans le dernier cours, car n'ayons pas peur de le dire : l'exercice sur la calculette de Windows ne sert pas à grand chose.

Bon tout d'abord on regarde, avec Stup_Pe par exemple, en quel langage ce crackme a été programmé. On voit qu'il a été fait en assembleur. Donc aucun signe de compression quelconque.

Donc maintenant on examine ce crackme. Il y a comme la plupart du temps 2 champs à remplir : le nom et le serial. Le bouton "Ok" est grisé, en remplissant les cases il ne s'active toujours pas donc on en conclut que le bouton se dégrisera lorsque le serial sera bon (élémentaire mon cher Watson). Pour savoir s'il faut ou non débloquer le bouton, le crackme est obligé de vérifier le serial à chaque fois qu'on tapera quelque chose. Contrairement à d'autres crackme où la vérification du serial s'effectue uniquement au moment de cliquer sur "Ok". Il va donc falloir qu'on rajoute un bouton dans ce crackme pour ensuite faire afficher une messagebox en cliquant dessus pour nous donner le bon serial par rapport au nom qu'on aura précédemment tapé.

Ajout d'un bouton

On ouvre donc le crackme avec votre éditeur de ressources favori. On va dans 'Dialog' pour rajouter un bouton qu'on appellera "Serial". Pour cela regardons comment sont faits les autres boutons.

Avec Restorator :

Après avoir cliqué sur [abI] ou 'Viewer' --> 'Edit Mode' on voit :

DEFPUSHBUTTON "&OK" , 1 , 37 , 70 , 50 , 16 , WS_DISABLED

  • DEFPUSHBUTTON : type de bouton

  • "&OK" : texte du bouton (le & signifie que la lettre O sera souligné avant de pouvoir permettre le raccourci clavier ALT+O)

  • 1 : l'ID du bouton

  • 37 : position dans la fenetre en horizontal

  • 70 : position dans la fenetre en vertical

  • 50 : longueur du bouton

  • 16 : largeur du bouton

  • WS_DISABLED : bouton grisé

Ainsi la syntaxe est la suivante :

TYPE Texte , ID , x , y , longueur , largeur , options

Donc on va rajouter :

DEFPUSHBUTTON "&Serial", 3, 67, 90, 50, 16

On mettra 3 comme ID car le 1 est déjà attribué au bouton "OK" et le 2 au bouton "Annuler". On fait F7 pour visualiser ce que ça donne. Le nouveau bouton colle un peu trop au bas de la fenêtre. On va donc augmenter la hauteur de la fenêtre du crackme.

On change : MYDIALOG DIALOG 0, 0, 186, 106 par MYDIALOG DIALOG 0, 0, 186, 116. Et voilà. Vous pouvez organiser comme vous le voulez les boutons. On appuie sur F8 pour valider et on enregistre.

Avec ResHacker :

Pour ceux qui n'aiment pas taper du code pour créer le bouton un peu "à l'aveugle" comme on vient de la faire avec Restorator utilisez plutôt ResHacker.

Faîtes un clic droit sur l'aperçu du crackme et sélectionner "Insérer un contrôle". Cliquez ensuite sur l'icône représentant un bouton "Ok" pour ajouter un bouton (BUTTON in english ;-) ), sélectionnez "BS_DEFPUSHBUTTON" dans style. Dans "text" tapez ce que vous voulez (pour moi ce sera "Serial") et surtout n'oubliez pas de bien mettre "3" dans l'ID. Et pour finir validez.
Vous pouvez voir votre bouton sur l'aperçu. Déplacez-le avec la souris où vous le souhaitez (vous pouvez également déplacer les autres éléments à votre guise). Une fois vos changements effectués, cliquez sur "Enregistrer" à coté de "Cacher". Puis 'Fichier' --> 'Enregistrer sous...' pour sauvegarder votre crackme modifié.

Pour info : Pour dégriser le bouton dans les 2 éditeurs il suffit de supprimer "WS_DISABLED". Le bouton sera ainsi dégrisé. Cela peut vous servir pour vos prochains cracks. Mais si on essaye cette méthode avec ce crackme, cela marchera (il affichera le bon message) si vous ne tapez rien. Mais dès que vous taperez votre nom le bouton redeviendra grisé car il vérifie à chaque frappe de clavier.

Ajout de la msgbox

Bon alors on ouvre le crackme avec WinDasm. Et on va essayer de trouver le moment où il regarde si un bouton a été cliqué ou non. L'ID du bouton "Ok" est 0001. Donc on cherche "0001" en regardant si on voit pas un truc du genre "cmp [registre], 1" (vous pouvez le faire également avec le bouton "Annuler"). Et effectivement on voit un :

:00401092 6683F801 cmp ax, 0001 --> regarde si c'est le bouton "Ok" qui a été cliqué
:00401096 7518 jne 004010B0 --> saute si ce n'est pas le bouton "Ok"
:00401098 6A30 push 00000030

* Possible StringData Ref from Data Obj ->"Enregistrement..."
|
:0040109A 6816304000 push 00403016

* Possible StringData Ref from Data Obj ->"Merci de vous " --> le bon message !

Et en 004010B0 :

:004010B0 6683F802 cmp ax, 0002 --> regarde si c'est le bouton "Annuler" qui a été cliqué
:004010B4 0F85C9000000 jne 00401183 --> si ce n'est pas "Annuler" alors saute

Donc là il y a pleins de solutions différentes, moi je vais changer le cmp ax, 0001 pour mettre à la place un JMP qui nous emmènera vers notre bout de code. Dans ce code on mettra :

  • cmp ax, 0001 --> on remet le test du "Ok"

  • je 00401098 --> si c'est "Ok" va vers bon message

  • cmp ax, 0003 --> regarde si c'est le bouton "Serial" qui a été cliqué

  • jne 004010B0 --> si ce n'est pas Serial saute en 004010B0 qui vérifiera si c'est le bouton "Annuler"

  • push [style de la msgbox] |

  • push [titre de la msgbox] |

  • push [message de la msgbox] | Affiche la msgbox

  • push 00 |

  • call messageboxa |

  • jmp 00401183 --> retourne au programme

Recherche du serial

Maintenant on va voir où le programme stocke le bon serial afin de l'afficher avec la msgbox.

:00401090 753B jne 004010CD --> saute si c'est pas le bon serial
:00401092 6683F801 cmp ax, 0001 --> regarde si c'est le bouton "Ok" qui a été cliqué
:00401096 7518 jne 004010B0 --> saute si c'est pas le bouton "Ok"
:00401098 6A30 push 00000030

* Possible StringData Ref from Data Obj ->"Enregistrement..."

Comme le programme vérifie tout le temps le serial (comme expliqué dans l'introduction) on va voir où nous mène le JNE. On tombe sur des API GetDlgItemTextA (pour info le "A" signifie qu'il fonctionne en 32 bits). Voici leur fonctionnement :

La fonction GetDlgItemText retrouve le titre ou le texte associé à un control dans une boîte de dialogue.

UINT GetDlgItemText (
HWND hDlg, // handle de la boîte de dialogue.
int nIDDlgItem, // n°ID du control
LPTSTR lpString, // adresse du buffer recevant le texte
int nMaxCount // nombre maximal de caractères formant la chaîne.
);

En assembleur il faut inverser, ce qui donne donc :

  • push [nombre maximal de caractères formant la chaîne]

  • push [adresse du buffer recevant le texte]

  • push [n°ID du control]

  • push [handle de la boîte de dialogue]

  • call GetDlgItemText

Ainsi voici ce que l'on a :

:004010D8 6800020000 push 00000200 --> nombre maximal de caractères
:004010DD 6850344000 push 00403450 --> le serial que nous avons tapé est stocké à cette adresse

* Possible Reference to Dialog: MYDIALOG, CONTROL_ID:03E9, ""
|
:004010E2 68E9030000 push 000003E9 --> l'ID du champ pour le serial
:004010E7 FF7508 push [ebp+08] --> handle

* Reference To: USER32.GetDlgItemTextA, Ord:0102h
|
:004010EA E8CD000000 Call 004011BC --> appel de l'API
:004010EF 6800020000 push 00000200 --> nombre maximal de caractères
:004010F4 6850324000 push 00403250 --> le nom que nous avons tapé est stocké à cette adresse

* Possible Reference to Dialog: MYDIALOG, CONTROL_ID:03E8, ""
|
:004010F9 68E8030000 push 000003E8 --> l'ID du champ pour le nom
:004010FE FF7508 push [ebp+08] --> handle

* Reference To: USER32.GetDlgItemTextA, Ord:0102h
|
:00401101 E8B6000000 Call 004011BC --> appel de l'API
:00401106 8D3550324000 lea esi, dword ptr [00403250] --> met notre nom dans ESI
:0040110C 8D3D55364000 lea edi, dword ptr [00403655] --> met ce qui est à l'adresse 00403655 dans EDI

Pour savoir ce que contient 00403655 lançons le débuggeur. On fait "Run", on tape un nom et un serial bidon. Ensuite dans la fenêtre des registres (celle en bas à gauche) tapez 00403655 dans le champ "User Addr 1". Après il suffit de cliquer en dessous sur "UA1". Et là à droite du bouton vous pourrez apercevoir un numéro ! Tapez ce serial dans le crackme tout en gardant votre nom. Et là ça marche : le bouton "Ok" se dégrise et affiche le bon message.

Pendant qu'on y est on va chercher l'adresse de l'API MessageBoxA. On clique sur les Imports on double clique plusieurs fois pour être sûr sur l'API MessageBoxA. Et on tombe sur "Call 004011C2"

Bon maintenant qu'on sait où se situent le serial et l'adresse de l'API, il va falloir trouver de l'espace dans le crackme pour insérer notre code.

Mise en place de notre code

On ouvre le crackme avec l'éditeur hexa et on recherche de la place (là où il y a des octets vides c'est à dire avec des 00). Allez hop il y a de la place de l'offset 750 jusqu'à 800. Ça devrait suffire. Bon on va commencer à l'offset 760 pour éviter de trop coller au texte qui est au dessus. Il va falloir trouver la correspondance de l'offset avec l'adresse WinDasm. Pour cela on prend une ligne au hasard dans Windasm, par exemple 004010CD, et on regarde l'offset, ici 4CD. On fait la différence (avec la calculatrice de Windows en mode scientifique et en hexa) : 004010CD - 4CD = 00400C00.

Donc avec notre offset 760 cela donne : 760 + 00400C00 = 00401360. Ce qui nous donne l'adresse Windasm.
Bon il n'y a plus qu'à rentrer notre code. On va voir ça avec le débuggeur de WinDasm avec le Patch Code. On pose un break point sur la ligne du :

:00401092 6683F801 cmp ax, 0001

Pour qu'il break cliquez sur "Serial". Et on remplace par un JMP 00401360. N'oubliez pas de copier la correspondance si vous voulez recopier le code hexa dans l'éditeur hexadécimal par la suite. Ce qui nous donne :

:00401092 E9C9020000 jmp 00401360

Mais il faut remplacer ces 2 lignes :

:00401092 6683F801 cmp ax, 0001
:00401096 7518 jne 004010B0

Et là il y a 6 octets (66-83-F8-01-75-18) contre 5 pour notre JMP. Donc on va mettre un NOP à la place du 6ème octet pour nettoyer comme il faut :

:00401092 E9C9020000 jmp 00401360
:00401097 90 nop

Donc on applique le changement et on fait un F7 pour avancer dans le code vers nos bytes libres (mais plus pour longtemps ;-) ).

Et on va entrer le code suivant (les significations de chaque ligne ont été expliquées plus haut) :

:00401360 6683F801 cmp ax, 0001
:00401364 0F842EFDFFFF je 00401098
:0040136A 6683F803 cmp ax, 0003
:0040136E 0F853CFDFFFF jne 004010B0
:00401374 6A40 push 00000040 --> Cf. cours précédent pour choisir le type de messagebox (ici Informations - Ok)
:00401376 6850324000 push 00403250 --> Titre : notre nom
:0040137B 6855364000 push 00403655 --> Message : le bon serial
:00401380 6A00 push 00000000
:00401382 E83BFEFFFF call 004011C2 --> Appel l'api MessageBoxA
:00401387 E9F7FDFFFF jmp 00401183

Pour tester, on applique, on enlève le BP et on clique sur "Run". On retape un nom puis on clique sur "Serial" et là apparaît un beau message avec notre nom en titre et le serial en message. On recopie le serial dans le champ prévu à cet effet et là le bouton "Ok" se dégrise et ça marche.

Vous n'avez plus qu'à faire les modifications dans votre éditeur hexa. Je ne vous explique plus comment faire, je vous fais confiance ;-) (sinon voir le dernier cours). Je rappelle qu'il faut commencer à l'offset 760.

Voilà ce cours se termine. J'espère que vous avez compris et que ce cours vous a plu. N'hésitez pas à me demander si vous avez un problème, une question, une suggestion ou même une critique, soit par l'intermédiaire du forum : http://www.forumcrack.new.fr/ ou par email : deamon.crack (at) netcourrier.com

Deamon, le 31 août 2003


Cours n°14

Nag d'un crackme en Visual Basic

Objectif :

Enlever le Nag d'un crackme.

Niveau :

Moyen

Ce qu'il vous faut :

- Un éditeur hexadécimal

- Le cracke-me (40 Ko)

Introduction

On va s'attaquer à un crackme en Visual Basic. Mais on va uniquement s'occuper de la partie Nag. Pour info le serial et le 'Word code' sont visibles dans les Text String Referenced avec OllyDbg par exemple (accessible en faisant clic droit : Search For --> All Referenced Text Strings).

En voyant les outils nécessaires pour ce cours vous avez dû être surpris ! Car pour cette fois ci nous n'aurons besoin que d'un éditeur hexadécimal.

Vous avez pu voir qu'un vilain nag avec marqué "NAG" dessus (comme c'est original) apparaît au lancement du programme, avant même que la fenêtre principale ne s'affiche. Le Nag comporte un Timer de 5000ms qui une fois les 5s passées, ferme le nag pour ouvrir la fenêtre principale.

[Éditeur hexadécimal] 1 - 0 [Visual Basic]

On va donc étudier ce crackme avec l'éditeur hexadécimal. Le programme étant fait en Visual Basic 6 on va donc chercher dans l'éditeur : "VB5!" ! Logique non ? Cela marche pour les programmes faits avec Visual Basic 4,5 et 6. Et dans ces 3 cas il faudra chercher "VB5!". Et on tombe ici :

Voici les explications de tout cela : on a donc notre "VB5!" en bleu clair en haut. En dessous, en vert, c'est l'en-tête de projet VB qui contient l'adresse des en-têtes fenêtres qui est ici en jaune. On va donc aller à l'en-tête des fenêtres. Ici on a 50|12|40|00 ce qui fait en bytes inversés (on lit les octets de droite à gauche) : 00|40|12|50. On va donc aller à l'offset 1250h (on ne s'occupe pas du 0040) qui se trouve juste en dessous.
En VB les programmes sont faits de plusieurs fenêtres appelées Form (une pour le menu, une pour la fenêtre de serial,... et une pour le NAG !!!).

Pour les en-têtes des Form on a en rouge la signature, c'est à dire le nombre d'octets de l'en-tête. Normalement les en-têtes font 50h octets ce qui fait 80 en décimal (dans l'éditeur une ligne fait 16 octets donc 16*5=80, l'en-tête s'étend sur 5 lignes donc). En Jaune on a l'adresse de la Form, l'endroit où on trouvera tout le contenu de la form.

Et en rose/violet, le plus intéressant, c'est l'attribut de la Form. C'est ça que nous allons modifier.

Pour connaître quel Form appartient à quelle fenêtre on peut utiliser des programmes comme VBEditor ou VBReformer. Mais sinon on peut aller voir ce qu'il contient dans l'éditeur hexa.

Pour trouver la bonne adresse de la Form il faut prendre l'offset donné et rajouter 64h. Par exemple pour le premier dont l'adresse est noté B0|3E|40|00 donc à l'offset 3EB0 cela nous donne 3EB0+64=3F14 (la calculatrice Windows fait les calculs en hexa en mode scientifique).
A l'offset 3F14 on voit "Form1" et "Serial Section". C'est donc la fenêtre du Serial.
Donc on fait pareil avec les 5 autres adresses. Et à la fin on trouve que la Form du NAG est la 3 et la fenêtre principale du crackme est la 4.

Form1 - Serial
Form2 - Name serial
Form3 - NAG
Form4 - Fenêtre Principal
Form5 - Word Code
Form6 - About

On va donc inverser les attributs de ces fenêtres pour que ce soit la fenêtre principale qui s'ouvre au démarrage et non le nag.

L'attribut (en violet) du nag est 10|01 soit 0110h et la Form4 est 00|07 soit 0700h. Quand un attribut est xx10h c'est que c'est la form qui se lance au démarrage. Les autres étant à xx00h principalement.
On va donc mettre 0010h à la place de 0110h (00|01 au lieu de 10|01) et 0710h à la place de 0700h (10|07 au lieu de 00|07).
En faisant ça, la fenêtre principale se lancera au démarrage du programme. Ce ne sera plus le Nag.

On enregistre, on teste et bingo ! Ca marche !

Donc voici comment on peut modifier un programme fait en Visual Basic, sans utiliser désassembleur ou débuggeur.

Deamon, le 06 décembre 2003


Cours n°15

Crackme avec KeyFile

Objectif :

1° Méthode brute : le cracker tout bêtement ! :-)

2° Méthode plus "intelligente" je dirais : générer un keyfile valide pour ce crackme.

Niveau :

Moyen - Elevé

Ce qu'il vous faut :

- Un éditeur hexadécimal

- OllyDbg (qui va remplacer WinDasm ; aucune connaissance de ce logiciel est nécessaire dans ce cours, je vous expliquerais le fonctionnement au fur et à mesure ; la version utilisée ici est la 1.09d)

- La calculatrice Windows (ou tout autre qui permet de faire des calculs hexadécimaux)

- Le crackme (9 Ko)

Introduction

Nous allons dans ce cours étudier un type d'enregistrement différent de ceux que nous avons rencontré au cours de mes anciens tutoriaux. Ici pas de serial, ni de nag-screen ou autres mais c'est de KeyFile qu'il est question. Pour ceux qui ne connaissent pas, le fonctionnement est le suivant : pour vérifier si le programme est enregistré celui-ci va regarder dans un fichier (le plus souvent c'est un .ini, .dat, ...) qui contient une clé. Si la clé est valide le programme est enregistré.

1° Cracker ce crackme (pléonasme devenu habituel dans mes cours !)

Nous allons donc lancer le crackme pour voir ce qu'il nous demande. Il nous affiche une message box affichant : "Your time-trial has ended... Please register and copy the keyfile sent to you to this directory". Pour les anglophobes voici la traduction : "La période d'essai est terminé... Veuillez vous enregistrer et copier le fichier clé, que vous avez reçu, dans ce dossier". Même pas eu le temps d'utiliser ce crackme et la période d'essai est déjà terminée ! Quelle arnaque ! :-P

Bon alors pour ce cours pas de WinDasm, on va apprendre à utiliser OllyDbg car je trouve qu'il est mieux, plus puissant et de plus en plus utilisé. D'ailleurs c'est moins facile de cracker ce crackme avec WinDasm car il ne trouve aucune String Data References. Donc on lance ce logiciel et on ouvre notre crackme qui tremble déjà de peur, son heure approche ! :-D
Normalement après avoir désassemblé le crackme vous devriez tomber nez à nez avec notre vilain message. Mais on va faire comme si on l'avait pas vu pour ne pas éveiller les soupçons... On va plutôt le prendre à revers ! :-p Alors discrètement on va faire clic droit dans le cadre où il y a les lignes de code, puis "Search For" --> "All referenced strings text", ce qui correspond au Strings Data References de WinDasm. Autre méthode : cliquer sur l'icône du "R" sur fond bleu (mais il faut obligatoirement avoir préalablement déjà chargé les SDR au moins une fois avec le menu). Une fois la fenêtre des Strings ouverte, double-cliquez sur le mauvais message pour vous y rendre. Bon vous allez tomber au même endroit qu'il y a 30 secondes, mais c'est juste pour vous montrez comment rechercher les messages.

Voici une vue globale d'OllyDbg :

1) Cette flèche signifie que l'instruction peut sauter vers un autre endroit, pour voir où le saut ira on peut cliquer sur la ligne et une flèche nous montrera. En mode pas à pas lorsque la flèche est grise cela signifie que le saut n'est pas effectué.
2) Cette flèche indique que cette instruction est appelée par un saut, c'est l'arrivée d'un saut.
3) La ligne 004010C1 appelé par un saut, étant sélectionnée, une flèche nous montre quel saut nous amène sur cette instruction.
4) Très utile car cela nous indique les valeurs que le programme manipule, ainsi pour l'instruction MOV AL,BYTE PTR DS:[EBX+40211A] , dans cette fenêtre est indiquée la valeur de DS:[EBX+40211A] (ici 33, en hexa bien sur) et celle de AL (AL est une partie de EAX et correspond aux bits de poids faibles, pour simplifier AL est les 2 premiers chiffres en partant de la droite de EAX donc ici 01). Quand c'est possible on nous donne également le caractère ascii auquel correspond la valeur, ici on nous indique que 33h correspond au caractère "3". Ce qui est utile quand le programme manipule les caractères d'un serial par exemple. C'est ici aussi que vous pouvez voir si un saut s'effectue ou non ("Jump is taken" ou "Jump is not taken").
5) Bouton pour voir les Strings Data References.
6) Les registres et ses valeurs. Peut aussi afficher en ascii pour un nom ou serial par exemple.
7) Les flags. Peut être utile pour inverser un saut. Par exemple si un JZ (=JE) ne saute pas mais vous voulez qu'il saute il suffit de double-cliquez sur la valeur du Flag Z pour l'inverser.
8) Nous indique ce qui est poussé sur la pile (avec PUSH). On peut y voir passer des serials parfois ! :-P
9) Permet de voir si le programme qu'on débuggue est en exécution, en pause (s'il break)...

Donc voilà les points importants d'OllyDbg. Maintenant on va regarder comment cracker notre petit programme. On voit à l'endroit du message :

00401078 . 83F8 FF CMP EAX,-1 ; |
0040107B . 75 1D JNZ SHORT due-cm2.0040109A ; |
0040107D . 6A 00 PUSH 0 ; |/Style = MB_OK|MB_APPLMODAL
0040107F . 68 01204000 PUSH due-cm2.00402001 ; ||Title = "Duelist's Crackme #2"
00401084 . 68 17204000 PUSH due-cm2.00402017 ; ||Text = "Your time-trial has ended... Please register and copy the keyfile sent to you to this directory!"
00401089 . 6A 00 PUSH 0 ; ||hOwner = NULL
0040108B . E8 D7020000 CALL <JMP.&USER32.MessageBoxA> ; |\MessageBoxA
00401090 . E8 24020000 CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess
00401095 . E9 28010000 JMP due-cm2.004011C2
0040109A > 6A 00 PUSH 0 ; /pOverlapped = NULL

Il y a donc un test pour savoir si EAX = -1 et si ce n'est pas le cas il saute après le mauvais message. Nous verrons par la suite à quoi cela correspond. On va donc remplacer le JNZ par un JMP (à noter que JZ=JE et JNZ=JNE, ce sont les mêmes sauts, sous OllyDbg ils sont plutôt notés JZ/JNZ et sous WinDasm JE/JNE). Pour tester dans OllyDbg on double-clique sur l'instruction JNZ et on remplace JNZ SHORT 0040109A par JMP SHORT 0040109A. On valide et on regarde si ça marche. On lance donc le crackme dans OllyDbg avec F9 (les raccourcis claviers des fonctions principales sont identiques à celle du débuggeur de windasm : F2 pour poser un BreakPoint, F9 pour Run, F7 : Step Into, F8 : Step Out...).

Alors maintenant le message n'est plus le même puisqu'il nous dit : "Your current keyfile is invalid... Please obtain a valid one from the software author!". Message que nous retrouvons un peu en dessous du précédent. Le programme s'étant fermé, il faut réinitialiser OllyDbg en appuyant sur CTRL+F2.

Bon donc ce saut ne suffit pas, mais il faut tout de même le modifier. Mais après la réinitialisation les modifications sont annulées (de toute façon les modifications dans OllyDbg tout comme le débuggeur de WinDasm ne sont que temporaires). Donc il va falloir refaire la modification sur le JNZ. On va pour cela poser un breakpoint dessus. On Run, il breake sur le JNZ. Là on va modifier le flag Z en double-cliquant sur sa valeur qui est ici à 1 pour le faire passer à 0. La flèche du saut devient rouge et le "Jump is NOT taken" se transforme en "Jump is taken". Et maintenant on va avancer en pas à pas avec F8 jusqu'au prochain saut qui pourrait nous emmener sur le mauvais message. Et on arrive ici :

004010AE . 85C0 TEST EAX,EAX
004010B0 . 75 02 JNZ SHORT due-cm2.004010B4
004010B2 . EB 43 JMP SHORT due-cm2.004010F7
004010B4 > 33DB XOR EBX,EBX

Donc là si le JNZ saute (ce qu'il ne fera pas si vous avez bien regardé) il passera au dessus du JMP. Ce dernier sautera à l'adresse 004010F7. Regardons ce qui se trouve à cette adresse : notre mauvais message "Your current keyfile is invalid...". Il faut donc faire sauter ce JNZ. Alors lorsqu'on s'arrête sur cette ligne en avançant en pas à pas, on fait la même chose que tout à l'heure : modifier le flag Z pour qu'il saute. Puis on tombe sur :

004010BF . 7C 36 JL SHORT due-cm2.004010F7

Il saute sur l'adresse du mauvais message donc on le noppera ou l'inversera. Pour modifier le flag ce n'est plus le Z car le saut est JL (Jump if Less = Saute si moins/inférieur) mais le flag S (Sign Flag). Ici même topo :

004010D6 . 7C 1F JL SHORT due-cm2.004010F7

Et donc juste avant le mauvais message il y en a encore un autre (le dernier ?) à transformer en JMP et qu'on passera avec le flag Z :

004010F5 . 74 1D JE SHORT due-cm2.00401114

Maintenant on croit en avoir fini, car on a déjà dû modifier 6 sauts pour en arriver là, mais non il en reste encore un plus bas qui saute lui aussi vers 004010F7 :

00401155 .75 A0 JNZ SHORT due-cm2.004010F7

Et voilà ça devrait être bon ! :-) Il ne semble pas y en avoir d'autre donc après avoir modifié le flag Z pour ce dernier on fait F9 pour continuer le programme... Et là : "This program is registered to our dear user :". Bon forcément il n'y a pas notre nom. Si vous voulez afficher votre nom lisez la suite :-) on va faire ça dans la deuxième partie de ce cours.

Donc pour résumer voici les sauts que nous avons à modifier :

0040107B . 75 1D JNZ SHORT due-cm2.0040109A ---> JMP
004010B0 . 75 02 JNZ SHORT due-cm2.004010B4 ---> JMP
004010BF . 7C 36 JL SHORT due-cm2.004010F7 ---> JNL ou NOP
004010D6 . 7C 1F JL SHORT due-cm2.004010F7 ---> JNL ou NOP
004010F5 . 74 1D JE SHORT due-cm2.00401114 ---> JMP
00401155 . 75 A0 JNZ SHORT due-cm2.004010F7 ---> NOP ou JZ

Mais maintenant que nous avons vu que le bon message commençait ici : [00401157 . 6A 00 PUSH 0 ; /lParam = NULL] on peut donc mettre à la place du premier saut (0040107B . 75 1D JNZ SHORT due-cm2.0040109A) un JMP 00401157 pour ainsi sauter directement vers le bon message. Ça marche et il n'y a qu'un endroit à modifier. Même si ça empiète sur le premier mauvais message ce n'est pas grave car il ne passera plus jamais par là ! :-P

Ce qui donne à la place de :

0040107B /75 1D JNZ SHORT due-cm2.0040109A
0040107D |6A 00 PUSH 0
0040107F |68 01204000 PUSH due-cm2.00402001 ; ASCII "Duelist's Crackme #2"

On aura :

0040107B /E9 D7000000 JMP due-cm2.00401157
00401080 |90 NOP
00401081 |90 NOP
00401082 |90 NOP
00401083 |90 NOP

Petite info : dans OllyDbg lorsque vous modifiez une ligne, le fait de cocher "Fill with NOP's" remplit les octets vides par des NOP comme pour l'exemple au-dessus. Par exemple si vous avez un JMP qui donne [E9D7000000] en hexa et que vous voulez mettre un petit JNZ de type [751D], vous aurez 3 octets de "libre" du JMP. Ceux là seront remplacés par des NOP pour ne pas perturber le programme. Donc le mieux est de laisser cette case cochée. OllyDbg pemet d'enregistrer les modifications effectuées sans avoir à passer par un éditeur hexa. Pour cela on fait clic droit dans le cadre du listing des instructions puis 'Copy to executable' -> 'All modifications' et on clique sur Copy All. Là une petite fenêtre s'ouvre on fait clic droit dedans et 'Save File' et on enregistre sous un autre nom.

2° Créer un KeyFile valide

Bon on recommence tout depuis le début. On ouvre avec OllyDbg le crackme (et pas celui déjà modifié ;-) ). On va vers notre premier message d'erreur ("Your time-trial..."). Et on remarque que juste au dessus l'API CreateFileA, donc l'accès à un fichier. Et dans les paramètres de l'API que voit-on ?

0040106E . 68 79204000 PUSH due-cm2.00402079 ; ||FileName = "due-cm2.dat"
00401073 . E8 0B020000 CALL <JMP.&KERNEL32.CreateFileA> ; |\CreateFileA

FileName="due-cm2.dat" !! Ça ne serait pas notre keyfile par hasard ? Pour le savoir on va créer un fichier nommé "due-cm2.dat" dans le dossier du crackme. Mais on ne va pas le laisser vide on va (avec le bloc-notes par exemple) mettre "123456" dedans (sans les guillemets bien sûr !). Vous enregistrez et vous lancez le crackme (en dehors d'OllyDbg). Et là le deuxième message d'erreur apparaît : "Your current keyfile is invalid... Please obtain a valid one from the software author!". Bon on va voir pourquoi le contenu du fichier n'est pas bon. Pour cela direction OllyDbg.

Donc là on comprend mieux à quoi sert le saut situé entre le CreateFileA et le 1er message :

00401078 . 83F8 FF CMP EAX,-1
0040107B 75 1D JNZ SHORT due-cm2.0040109A --> si le fichier n'existe pas, alors ne saute pas et passe par le 1er message.

Ce saut nous emmène sur l'API ReadFile (tout le monde aura deviné à quoi cette API sert :-) ) :

0040109A > 6A 00 PUSH 0 ; /pOverlapped = NULL
0040109C . 68 73214000 PUSH due-cm2.00402173 ; |pBytesRead = due-cm2.00402173
004010A1 . 6A 46 PUSH 46 ; |BytesToRead = 46 (70.)
004010A3 . 68 1A214000 PUSH due-cm2.0040211A ; |Buffer = due-cm2.0040211A
004010A8 . 50 PUSH EAX ; |hFile
004010A9 . E8 2F020000 CALL JMP.&KERNEL32.ReadFile ; \ReadFile
004010AE . 85C0 TEST EAX,EAX

On va donc breaker juste après l'API sur le TEST EAX, EAX. On sélectionne la ligne est on fait F2 (le bout de la ligne devient rouge). On Run (F9) et ensuite on réfléchit. On peut voir (dans la fenêtre numérotée 4 ou 6 sur le schéma au-dessus) que EAX est à 1. On va avancer en pas à pas avec F8 (ou F7 mais ce dernier va vous faire rentrer dans les CALL et là on n'en aura sans doute pas besoin). Pour mieux vous repérer vous pouvez mettre des commentaires sur les lignes en faisant clic-droit et "Comment" (;) sur la ligne que vous souhaitez.

Le saut JNZ de la ligne suivante saute au-dessus du JMP qui nous mène au bad message. Ce qui est bien donc. Ensuite on trouve deux XOR sur EBX lui-même et ESI également ce qui a pour effet de mettre ces registres à 0. Les XOR sont souvent utilisés pour réinitialiser les registres. On arrive après les XOR ici :

004010B8 . 833D 73214000 CMP DWORD PTR DS:[402173],12 --> compare DS:[402173] à 12h
004010BF . 7C 36 JL SHORT due-cm2.004010F7 --> saute vers le mauvais message si DS:[402173] est inférieur à 12h

A ce moment DS:[402173] vaut 6, si on se souvient bien on à mis le nombre 123456 dans le keyfile. 6 correspond donc à son nombre de caractères. Il faut donc que le serial du keyfile fasse 12h lettres minimum (12 en hexa correspond à 18 en décimal, donc 18 caractères minimum). On note donc cette condition et on continue en changeant le flag S pour éviter le saut du JL. Et là on arrive sur une petite routine :

004010C1 MOV AL,BYTE PTR DS:[EBX+40211A] --> met le caractère dans AL
004010C7 CMP AL,0 --> compare le caractère à 00h
004010C9 JE SHORT due-cm2.004010D3 --> sort de la routine si le caractère = 00h
004010CB CMP AL,1 --> compare le caractère à 01h
004010CD JNZ SHORT due-cm2.004010D0 --> saute au dessus de INC ESI si le caractère est différent de 01h
004010CF INC ESI --> incrémente ESI
004010D0 INC EBX --> incrémente EBX = caractère suivant
004010D1 JMP SHORT due-cm2.004010C1 --> boucle au début de la routine
004010D3 CMP ESI,2 --> compare ESI à 2
004010D6 JL SHORT due-cm2.004010F7 --> Saute vers le Bad Boy si ESI < 2

J'espère qu'avec les couleurs c'est plus compréhensible ! :-) J'ai fait du mieux que j'ai pu.
Donc pour résumer en rose/rouge c'est la routine qui va d'abord mettre à chaque passage un caractère (le 1er puis le 2ème...etc) dans AL. Ensuite si le caractère est 00 (en hexa) il sort de la routine. Il trouvera un 00 quand il n'y aura plus de caractères. Après cela si le caractère = 01h il ne saute pas et incrémente ESI, sinon pour tout autre caractère il n'incrémente pas ESI car il saute par dessus. Et enfin il incrémente EBX pour passer au caractère suivant.

Quand vous êtes sur la ligne du MOV, vous pouvez voir la valeur du caractère étudié, le premier sera le "1", mais attention "1" est un caractère ascii et non une valeur. Sa valeur est 31h.

Donc pour éviter le mauvais message il faut que ESI soit égal ou supérieur à 2. C'est à dire qu'il y ait au moins deux caractères 01h dans le serial. On va donc mettre un serial valide. Pour mettre 01h il faut donc utiliser l'éditeur hexadécimal. Vous ne pourrez pas modifier le .dat à cet instant car le crackme est en exécution dans OllyDbg, on va donc noter à quel ligne on repartira (004010D6) et on réinitialise OllyDbg avec CTRL+F2. Si un message s'affiche faîtes "Oui", une erreur peut survenir mais c'est normal dans ce cas les breakpoints et les modifications seront annulés mais ce n'est pas grave. Une fois initialisé on peut modifier le .dat. On va donc mettre 18 caractères dont deux 01h. Dans HexDecCharEditor pour ajouter des bytes il faut faire "Edit" --> "Insert Bytes" ou la touche Ins.

On va donc mettre par exemple (en ascii) : 123456 789ABC DEFG (les 01h, ne pouvant pas être représentés en ascii, correspondent ici aux espaces).
Ce qui donne en hexa : 31|32|33|34|35|36|01|37|38|39|41|42|43|01|44|45|46|47
Je vous déconseille de mettre des caractères identiques dans le serial car après vous aurez des difficultés à savoir sur quel caractère travaille le crackme dans ses routines et ses tests. Bon donc on enregistre et on va regarder ce qu'il nous veut encore, ce crackme.

On pose un BreakPoint en 004010D6 comme on l'avait noté, car les tests d'avant doivent être corrects. On Run, ça break normalement et sur le JL on voit : "Jump is NOT taken" (enfin si vous avez bien rempli correctement le .dat). Le crackme a donc bien compté les deux 01h. On avance en pas à pas toujours : XOR EBX, EBX et XOR ESI, ESI : mise à 0 de ces deux registres. Et ensuite juste avant le deuxième mauvais message on rencontre une routine quasi-semblable à la précédente :

004010DC MOV AL,BYTE PTR DS:[EBX+40211A] --> met le caractère dans AL
004010E2 CMP AL,0 --> compare le caractère à 00h
004010E4 JE SHORT due-cm2.004010EF --> sort de la routine si le caractère = 00h
004010E6 CMP AL,1 --> compare le caractère à 01h
004010E8 JE SHORT due-cm2.004010EF --> sort de la routine si le caractère = 01h
004010EA ADD ESI,EAX --> ajoute la valeur du caractère à ESI
004010EC INC EBX --> caractère suivant
004010ED JMP SHORT due-cm2.004010DC --> boucle au début de la routine : et c'est reparti pour un tour :-p !
004010EF CMP ESI,1D5 --> compare ESI à 1D5h
004010F5 JE SHORT due-cm2.00401114 --> saute au dessus du bad boy si ESI = 1D5h

Explication pour ceux qui ne comprennent toujours pas mes belles routines colorées :-) : chaque caractère est placé tour à tour dans AL (donc dans EAX car je répète que AL est une partie de EAX). Si le caractère est 00h ou 01h alors il sort de la routine. Sinon il ajoute la valeur hexa du caractère dans ESI. Ainsi on aura dans ESI, à la fin de la routine, la somme des caractères situés avant le premier 01h ou 00h qui joue le rôle de séparateur (pour notre .dat ce sera 01h qui arrivera avant le 00h). Après la routine il vérifie que ESI (donc la somme des caractères) soit égale à 1D5h. Et si ce n'est pas le cas : PAF! Un "Your keyfile is invalid..." vous sautera à la figure ! Pour résumer : il faut donc que la somme des caractères situés avant le premier 01h soit égale à 1D5h.

Bon on note où on en est (004010F5) si on ne veut pas se perdre, on réinitialise tout ça et on va faire les modifications nécessaires. Pour notre exemple nous avons mis 6 caractères avant le 01h. On va donc prendre la calculette pour se débrouiller à avoir une somme de 1D5h (et oui car de tête c'est pas pratique le calculs hexadécimaux :-/ ). Pour la calculette mettez vous en Mode Scientifique et en "Hex". On va faire 1D5/6 ce qui donne 4E. Mais en hexa il n'y a pas de décimal donc 4E correspond à la partie entière. Pour trouver le reste vous devez savoir faire je pense ! :-) Bon je vais vous aider quand même ! ;-) On fait 4E*6 on trouve 1D4. Et 1D5-1D4=1 (cette soustraction à été calculée de tête là ! Balèze hein ? :-P ) donc on aura cinq 4E et un 4F (car 4E+1=4F). Ce qui nous fait un total de 1D5 ! Le compte est bon ! :-) On a donc dans l'éditeur hexa : 4E|4E|4E|4E|4E|4F|01|37|38|39|41|42|43|01|44|45|46|47.

On retourne à OllyDbg en posant un breakpoint sur 004010F5, on Run, ça break et on voit sur le JE : "Jump is taken" ! Ouais ça marche ! Et là on se dit que c'est fini car on saute après le deuxième mauvais message, mais comme on l'a vu en première partie il reste encore des vérifications ! Donc on avance prudemment en faisant attention aux sauts qui, tels des mines, peuvent vous faire sauter n'importe où ! :-P On passe par dessus le bad boy, on tombe sur une mise à 0 de ESI et une incrémentation de EBX. Pourquoi changer EBX ? Car EBX correspond toujours au numéro de position des caractères et donc il passe au caractère suivant. Rappelez vous on s'était arrêté au 01h à la routine précédente ! Et là à partir du INC EBX on tombe encore une routine, c'est donc reparti pour une routine colorée par mes soins :-) :

00401116 INC EBX --> caractère suivant
00401117 MOV AL,BYTE PTR DS:[EBX+40211A] --> met le caractère dans AL
0040111D CMP AL,0 --> compare le caractère à 00h
0040111F JE SHORT due-cm2.00401139 --> sort de la routine si le caractère = 00h
00401121 CMP AL,1 --> compare le caractère à 01h
00401123 JE SHORT due-cm2.00401139 --> sort de la routine si le caractère = 01h
00401125 CMP ESI,0F --> compare ESI à 0Fh
00401128 JNB SHORT due-cm2.00401139 --> sort de la routine si ESI > ou = 0Fh
0040112A XOR AL,BYTE PTR DS:[ESI+40211A] --> XOR entre ESI+40211A et le caractère (cf. explication plus bas)
00401130 MOV DWORD PTR DS:[ESI+402160],EAX --> met le résultat dans ESI+402160
00401136 INC ESI --> incrémente ESI = caractère suivant (cf. explication plus bas)
00401137 JMP SHORT due-cm2.00401116 --> boucle au début de la routine
00401139

Bon donc on commence avec le caractère qui est situé après le premier 01h. Si le caractère est 00h ou 01h (qui servent encore de séparateur) on sort de la routine. On la quitte également si ESI >= 0Fh.

Avant d'expliquer ce qu'il fait avec le XOR, voyons ce qui correspond à quoi. 40211A est l'adresse où est situé le serial. Donc EBX+40211A correspond au caractère placé en "EBX position" (donc au départ la position juste après le 01h, donc, ici, en 7ème position). Pour ESI+40211A, il faut savoir que juste avant la routine, ESI a été mis à 0. Donc ESI+40211A correspond au 1er caractère. Ainsi pour le XOR AL,BYTE PTR DS:[ESI+40211A] cela signifie qu'il fait une opération XOR entre le Nème caractère après le 01h (donc AL) et le Nème caractère du serial (ESI+40211A). Par exemple lors de la première boucle on aura un XOR entre le 1er caractère et le 7ème. Puis à la deuxième boucle : 2ème caractère XOR 8ème caractère. C'est compliqué à expliquer donc voici pour vous aider la correspondance en couleur : 4E|4E|4E|4E|4E|4F|01|37|38|39|41|42|43|01|44|45|46|47. Les XOR se font entre les valeurs de même couleur.

Le résultat de calcul est mis à l'adresse : ESI+402160. Si vous voulez voir ce que cela donne vous pouvez regarder dans la fenêtre en bas à l'adresse 402160 (+ESI mais comme celui-ci a varié de 0 à 6 les résultats commencent en 402160+0 donc bien 402160 et finissent en 402166) ce que cela donne (ici "yvw│││"). Nous verrons par la suite à quoi cela sert.

On continue notre pas à pas (pour éviter de faire en pas à pas les 6 boucles de la routine vous pouvez poser un BP juste à la sortie de la routine en 00401139 et appuyez sur Run pour s'y rendre immédiatement). Par la suite on trouve un INC EBX (pour passer au caractère suivant, donc caractère suivant le deuxième 01h) et une mise à 0 de ESI. Et ensuite encore une routine qui est strictement identique à la deuxième rencontrée à une différence près : CMP ESI,1B2 et non 1D5. Il faut donc que la somme des caractères après le deuxième 01h soit égale à 1B2h. On a 4 caractères après le deuxième séparateur. On fait donc 1B2/4 = 6C et il reste 2. Donc on aura trois fois 6C et une fois 6C+2 soit 6E : 4E|4E|4E|4E|4E|4F|01|37|38|39|41|42|43|01|6C|6C|6C|6E. On fait donc les modifications dans l'éditeur hexa (pensez à réinitialiser OllyDbg et noter à quelle ligne on est). On teste en lançant le .exe. Et là : BINGO ! Ça marche ! Mais il y a un petit détail qui ne va pas ! Notre nom ne ressemble à rien ! :-( On va donc voir comment il trouve le nom. On va donc breaker là où en était (00401155) et continuer à analyser le code. Une fois le programme breaké, on va regarder un peu en dessous pour avoir une vue globale de la suite. Et on remarque :

004011F4 > \68 60214000 PUSH due-cm2.00402160 ; /Text = "yvw│││"
004011F9 . 6A 01 PUSH 1 ; |ControlID = 1
004011FB . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004011FE . E8 5E010000 CALL <JMP.&USER32.SetDlgItemTextA> ; \SetDlgItemTextA

Le texte qu'il va afficher : yvw│││ ressemble étrangement à ce qu'on avait aperçu en 402160, là où on avait le résultat du XOR. Cette routine servait donc à trouver le nom. Maintenant il n'y a plus qu'à calculer pour arriver à afficher notre nom. On va essayer d'afficher "Deamon" (le nom fera forcément 6 caractères donc 6 lettres au maximum). On va donc décomposer mon nom "Deamon" en valeur hexa (pour cela soit vous prenez une table ascii qui est disponible dans HexDecCharEditor (l'icône du carré jaune :-) ) ou soit vous tapez dans la partie ascii de l'éditeur et sa correspondance en hexa s'affiche).

D|e|a|m|o|n donne donc 44|65|61|6D|6F|6E. Mais pour trouver les lettres le crackme calcule en faisant un XOR entre les caractères après le premier 01h et ceux avant. On a : 4E|4E|4E|4E|4E|4F|01|X1|X2|X3|X4|X5|X6|01|6C|6C|6C|6E. Pour avoir 44 en pour la première lettre ("D") il faut que 4E XOR X1 = 44 (la variable X1 correspondant au nombre recherché pour la première lettre, X2 pour la seconde...) donc X1 = 4E XOR 44 = 0A. Faites le calcul du XOR avec la calculatrice de windows. Donc voici ce que ça donne :

X1 = 4E XOR 44 = 0A
X2 = 4E XOR 65 = 2B
X3 = 4E XOR 61 =
2F
X4 = 4E XOR 6D = 23
X5 = 4E XOR 6F = 21
X6 = 4F XOR 6E =
21

Ce qui nous donne : 4E|4E|4E|4E|4E|4F|01|0A|2B|2F|23|21|21|01|6C|6C|6C|6E

Si votre nom est plus court ou plus long vous pouvez déplacer les séparateurs et augmenter le nombre total de caractères. Mais le nom ne dépassera pas les 15 caractères à cause du CMP ESI,0F. Et veillez à mettre le même nombre de lettres avant et après le 1er séparateur. Mais respectez la valeur des sommes des caractères du début et de la fin.

Attention : Si par exemple vous souhaitiez afficher un "N" dans votre nom, cela n'aurait pas marché car "N" = 4E et comme 4E XOR 4E = 00h cela aurait donc posé un problème (c'est la même chose pour 00h ou 01h). Il aurait donc fallu changer le 4E du départ par exemple en mettant 40 (ce qui fait 4E-40=E) et donc rajouter Eh à un autre pour que la somme soit toujours égal à 1D5h. Exemple : 40|5C|4E|4E|4E|4F|01|0E|39|2F|23|21|21|01|6C|6C|6C|6E. Ce qui revient au même car 4D+5C=4E+4E. Et ainsi le "N" en première place aurait donné X1 = 40 XOR 4E = 0E. Et donc la deuxième lettre si on garde le "e" (65h) donnerait : X2 = 5C XOR 65 = 39. Bon j'espère avec ça que vous aurez compris.

Récapitulatif :

- Nom du keyfile : due-cm2.dat
- 18 caractères minimum
- 2 caractères 01h minimum qui servent de séparateur
- 00h ou un vide pour indiquer la fin du keyfile
- La somme des caractères avant le premier 01h fait 1D5h
- La somme des caractères après le deuxième 01h fait 1B2h
- Les caractères du nom sont donnés par l'opération XOR entre les caractères d'avant et d'après le premier 01h

- Exemple valide avec le nom "Deamon" : 4E|4E|4E|4E|4E|4F|01|0A|2B|2F|23|21|21|01|6C|6C|6C|6E

Et avec ça, ça sera tout ! :-) Le crackme est cracké et avec la manière ! Et en plus vous savez désormais utiliser OllyDbg.

Deamon, le 02 et 03 mars 2004


Cours n°16

Brute-Forcer un crackme

Objectif :

1° Trouver le bon serial en utilisant une méthode par Brute-Force uniquement en modifiant le crackme.

Niveau :

Moyen - Elevé

Ce qu'il vous faut :

- OllyDbg et les quelques connaissances à son propos que j'ai données dans le cours précédent.

- Le crackme (30 Ko)

Introduction

L'attaque par brute force est une technique employée généralement lorsque le serial ne peut pas se trouver à la main (ou de tête, ça dépend avec quoi vous réfléchissez d'habitude), le brute force va donc tester toutes les solutions possibles jusqu'à trouver la bonne. C'est efficace mais cela peut aussi être très long, en effet si le mot de passe cherché est assez long (environ plus de 8 lettres) il va falloir des jours, des mois voire des années pour le trouver. Heureusement pour nous, l'auteur de ce crackme n'a pas été trop méchant et a mis un serial d'une longueur correct.

Ce crackme est tiré d'un site de challenges, le but du jeu : le programme doit nous donner le mot de passe du défi. Mais pour qu'il nous le donne il faut rentrer le bon serial. Donc théoriquement si on trouve uniquement le mot de passe sans le serial c'est bon. Mais nous allons voir qu'ici il faut obligatoirement l'un pour avoir l'autre.

Ne sachant pas vraiment programmer en assembleur, on ne va pas créer un programme pour brute-forcer le crackme mais utiliser le crackme afin qu'il se brute-force lui-même. Bon alors je vous préviens ce que je vais faire ne sera sans doute pas beau à voir (surtout pour les pros de la programmation en assembleur ;-) ) mais ce qui compte c'est qu'on arrive à trouver la solution. C'est à dire qu'il y a sûrement plus rapide, plus simple peut-être, mais vous aurez l'occasion d'y réfléchir après avoir lu ce tutorial.

Petit rappel : Qu'est-ce qu'une API ? "API" est l'acronyme de "Application Programming Interface". C'est une interface de programmation d'applications, contenant un ensemble de fonctions courantes de bas niveau, bien documentées, permettant de programmer des applications de « Haut Niveau ». On obtient ainsi des bibliothèques de routines, stockées par exemple dans des DLL ou des NLM (définition du site tout-savoir.net).

Dernière petite chose avant de commencer : pour ce cours comme pour le précédent nous allons utiliser OllyDbg car WinDasm est vieux et ça fait ringard de cracker avec ;-). Plus sérieusement OllyDbg est beaucoup plus efficace que WinDasm et de plus ce dernier n'évolue plus. Donc mettez WinDasm à la poubelle et prenez donc OllyDbg avec vous lors de vos prochaines soirées de cracking ! :-)

1° Prise de connaissance du crackme

On exécute le crackme pour voir de quoi il retourne. On voit qu'il faut taper un "keyword". Comme on est poli on tape "bonjour" : "Error !" soit-disant car le mdp (mot de passe) ne fait pas le bon nombre de caractères. En tâtonnant on trouve qu'il doit faire 5 caractères, on retrouvera ce résultat dans OllyDbg par la suite. En tapant un mot de 5 lettres on a le droit à un autre message d'erreur toujours en anglais nous disant cette fois que nous aurons plus de chance la prochaine fois (ça impressionne cette maîtrise de la langue anglaise quand même avec la traduction instantanée que je viens de faire là :-p ). En validant ce message le crackme se ferme. Rien de plus énervant pour cracker ce genre de crackme car en l'analysant on a besoin souvent de revalider le serial pour "refaire un tour". De plus lorsque l'on fait des modifications dans le programme, en le réinitialisant dans OllyDbg celles ci disparaissent. Donc c'est la première chose que nous allons faire : empêcher le crackme de se fermer.

Pour cela on ne va pas trop se prendre la tête. En ouvrant OllyDbg la première ligne sur laquelle on tombe c'est l'EntryPoint (en 401000) on pourrait donc le faire revenir ici après l'affichage du message d'erreur. Cela aurait pour effet de recréer une nouvelle fenêtre du crackme mais ce serait déjà nettement moins gênant. Mais tentons d'affiner un peu ce saut. En descendant un petit peu on aperçoit les APIs (dans la partie 'Comment' à droite des instructions en assembleur et notées en rouge normalement) LoadIconA, SendMessageA et SendDlgItemMessageA qui correspondent à l'affichage du crackme, on va donc se mettre un peu après. L'instruction suivant la 3ème de ces API est un JMP qui mène en 401141. On essaiera donc cette destination. Maintenant que celle-ci a été choisie il va falloir déterminer le lieu de départ. En regardant vite fait encore un peu en dessous on trouve 3 messagebox :

004010F5 . 68 44304000 PUSH CrackIt.00403044 ; |Text = "The Solution perhaps is 'AzulGranaEsMiColor'" --> Oh mais serait-il la solution que l'on cherche ? ... Que vous êtes naïf ! ;-) Cela serait beaucoup trop facile sinon. On va voir que "AzulGranaEsMiColor" sera décrypté en fonction du serial, c'est pour cela qu'il faut trouver le bon serial.

00401112 . 68 ED304000 PUSH CrackIt.004030ED ; |Text = "The Keyword is not n characters long" --> Si le serial ne fait pas le bon nombre de caractères.

0040112A . 68 B8304000 PUSH CrackIt.004030B8 ; |Text = "Best luck the next time :D" --> Le Bad Boy : si ce n'est pas le bon serial.

Pour le moment c'est uniquement du dernier qu'on a besoin car c'est après celui-ci que le crackme se ferme et en effet une API EndDialog se trouve juste après. C'est donc ici qu'il va falloir sauter.

00401137 6A 00 PUSH 0
00401139 FF75 08 PUSH DWORD PTR SS:[EBP+8]
0040113C . E8 AF000000 CALL <JMP.&user32.EndDialog> ; \EndDialog

On va donc tout simplement remplacer ce morceau par un JMP 00401141. Il va donc rester des instructions qui ne servent à rien car le JMP va juste remplacer les 2 bytes (octets) du PUSH 0, mais de toute façon il ne passera plus par ici donc on peut laisser, ce n'est pas gênant. Pour modifier la ligne on double clique sur l'instruction (pour info si vous double-cliquez sur la partie commentaire vous pourrez en mettre un commentaire, surprenant non ?, très pratique pour se repérer parfois et sur le code en hexa cela pose un BP. Je rappelle également que BP n'est pas une Baguette de Pain mais un BreakPoint). Remarquez qu'on aurait très bien pu nopper toute l'API car la ligne 401141 se trouve juste après, mais alors dans ce cas vive la finesse ! Après modifications on a donc :

00401137 /EB 08 JMP SHORT 00401141
00401139 |FF75 08 PUSH DWORD PTR SS:[EBP+8]
0040113C . |E8 AF000000 CALL <JMP.&user32.EndDialog> ; \EndDialog

Une fois cela fait on va enregistrer le fichier, pas forcément car c'est utile, quoique si le programme est réinitialiser on perdra la modification faite, mais surtout pour savoir comment faire pour ceux qui découvrent encore un peu OllyDbg. On fait donc clic droit puis 'Copy to executable' -> 'All modifications', on clique sur Copy All. Là une petite fenêtre s'ouvre on fait clic droit dedans et 'Save File' et on enregistre sous un autre nom de préférence pour avoir une sauvegarde. Maintenant on va pouvoir s'attaquer aux choses sérieuses.

2° Analyse du crackme

On s'intéresse désormais de plus près aux 3 msgbox qu'on vient de rencontrer :

004010E7 . 3D 9A020000 CMP EAX,29A
004010EC . 75 16 JNZ 00401104 --> Si EAX=29A alors continue sinon on saute au dessus de cette msgbox

004010EE . 6A 30 PUSH 30 ; /Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL
004010F0 . 68 11304000 PUSH 00403011 ; |Title = "Congratulations!"
004010F5 . 68 44304000 PUSH 00403044 ; |Text = "The Solution perhaps is 'AzulGranaEsMiColor'" --> Good boy :)
004010FA . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
004010FD . E8 00010000 CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA

00401102 . EB 33 JMP 00401137 --> revient en 401141 au "début" du programme
00401104 > 3D 4D010000 CMP EAX,14D
00401109 . 75 18 JNZ 00401123 --> si EAX=14D alors continue sinon on saute au dessus de cette msgbox

0040110B . 6A 10 PUSH 10 ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL
0040110D . 68 D3304000 PUSH 004030D3 ; |Title = "Uhhhhhhhhhhhhhh!!!!!!!!!!"
00401112 . 68 ED304000 PUSH 004030ED ; |Text = "The Keyword is not n characters long" --> mauvaise longueur du serial
00401117 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
0040111A . E8 E3000000 CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA

0040111F . EB 20 JMP 00401141--> revient en 401141 au "début" du programme
00401121 . EB 14 JMP 00401137

00401123 > 6A 10 PUSH 10 ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL
00401125 . 68 7A304000 PUSH 0040307A ; |Title = "Uhhhhhhhhhhhhhh!!!!!!!!!!"
0040112A . 68 B8304000 PUSH 004030B8 ; |Text = "Best luck the next time :D" --> Bad Boy :(
0040112F . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
00401132 . E8 CB000000 CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA

00401137 > EB 08 JMP 00401141

C'est donc avec la valeur de EAX que le crackme détermine si le serial est bon ou pas. A partir de ça il est facile de lui faire afficher le bon message, mais le problème comme je l'ai dit c'est qu'il ne nous donnera pas le bon mdp car ce dernier sera calculé en fonction du mauvais serial que nous aurons rentré. Vous n'avez qu'à essayer vous verrez bien qu'on obtient un mdp qui n'a pas de sens.

Juste au dessus du passage que je viens de détailler il y a une API intéressante :

004010CE . 6A 08 PUSH 8 ; /Count = 8
004010D0 . 68 08304000 PUSH 00403008 ; |Buffer = 00403008
004010D5 . 68 EA030000 PUSH 3EA ; |ControlID = 3EA (1002.)
004010DA . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004010DD . E8 14010000 CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA

004010E2 . E8 60000000 CALL 00401147

L'API GetDlgItemTextA permet de récupérer ce qui a été tapé dans une boite de dialogue et ici par conséquent cela doit être notre serial. Et, entre cette API et la première comparaison de EAX, il y a un CALL. Je ne vous cacherai pas que le serial est bien entendu vérifié dans ce CALL. Pour se rendre à l'adresse du CALL il y a deux façons : le bus ou le métro ! ... Je disais donc il y a deux façons : soit on regarde l'adresse de destination, ici 401147, et on y va à la main (bon vous avez de la chance car ici ce n'est pas loin) ou alors on est malin et on appuie sur Entrée après avoir sélectionné la ligne du CALL.

Au début du CALL il y a :

0040114B |. 68 08304000 PUSH 00403008 ; /String = ""
00401150 |. E8 CB000000 CALL <JMP.&kernel32.lstrlenA> ; \lstrlenA

00401155 |. 83F8 05 CMP EAX,5
00401158 |. 74 13 JE 0040116D

L'API lstrlenA permet de mesurer la taille d'une chaîne de caractères, ici il s'agit du serial. Pour vous le prouver posez donc un BP (avec F2 ou en double-cliquant sur le numéro de la ligne) sur la ligne du PUSH en 40114B. Lancez le programme avec F9, on tape un serial bidon "1234" de 4 caractères par exemple (enfin si mes calculs sont bons :-) ). Il breake normalement sur le push et vous voyez apparaître.

0040114B |. 68 08304000 PUSH 00403008 ; /String = "1234"
00401150 |. E8 CB000000 CALL <JMP.&kernel32.lstrlenA> ; \lstrlenA

Bon vous me croyez maintenant ? ;-) On notera au passage que notre serial est stocké en 403008, vous pouvez le voir dans la fenêtre du bas à cette adresse, cela aura son importance pour la suite.

Pendant qu'on est en débogage on va continuer avec. Les raccourcis sont les mêmes que pour le debuggueur de WinDasm c'est à dire : F7 Step into et F8 Step Over. On avance avec F8 (pour éviter de rentrer dans le CALL de l'API) jusqu'au CMP EAX, 5. Là on voit bien que EAX (en haut à droite dans la fenêtre des registres) est égal à 4 soit le nombre de caractères de notre serial. Et donc 4 étant différent de 5 (enfin jusqu'à présent c'est le cas il me semble) le JE ne saute pas et on va continuer tranquillement, après avoir mis EAX à 14D, vers un RETN en 40116C qui a pour but de nous faire sortir du CALL. Ainsi comme EAX = 14D nous allons avoir droit au message comme quoi il n'y a pas le bon nombre de caractères.

En revanche si le serial fait 5 caractères on va en 40116D. Bon alors maintenant on va passer la vitesse supérieure.

0040116D |> PUSHAD --> sauvegarde tous les registres en les mettant sur la pile
0040116E |. XOR EAX,EAX --> mise à 0 de EAX
00401170 |. XOR EBX,EBX --> idem pour EBX
00401172 |. MOV ESI,00403008 ; ASCII "12345" --> met notre faux serial dans ESI
00401177 |. MOV AL,BYTE PTR DS:[ESI] --> 1ere lettre du faux serial dans AL
00401179 |. MOV ECX,1 --> initialisation du compteur

0040117E |> /MOV BL,BYTE PTR DS:[ESI+1] --> 1ere lettre du faux serial dans BL
00401181 |. |CDQ --> convertit le mot signé de EAX en un quadruple mot dans <EDX:EAX>
00401182 |. |MUL EBX --> multiplie EAX par EBX, le résultat va dans EAX
00401184 |. |INC ESI --> ESI+1 : lettre suivante
00401185 |. |INC ECX --> ECX+1 : compteur
00401186 |. |CMP ECX,5 --> compare le compteur à 5
00401189 |.^ \JB SHORT 0040117E--> saute quand le compteur est inférieur ou égal à 5

0040118B |. MOV ESI,00403008 ; ASCII "12345" --> remet le serial bidon dans ESI
00401190 |. XOR ECX,ECX --> mise à 0 de ECX

00401192 |> /MOV BL,BYTE PTR DS:[ESI] --> 1ere lettre du faux serial dans BL
00401194 |. |ADD EAX,EBX --> EAX = EAX + EBX
00401196 |. |CDQ --> convertit le mot signé de EAX en un quadruple mot dans <EDX:EAX>
00401197 |. |MUL EBX --> EAX = EAX * EBX
00401199 |. |INC ESI --> lettre suivante
0040119A |. |INC ECX --> compteur + 1
0040119B |. |CMP ECX,5 --> compare le compteur à 5
0040119E |.^ \JB SHORT 00401192 --> saute quand le compteur est inférieur ou égal à 5

004011A0 |. CMP EAX,ED3C9A41 --> compare EAX et ED3C9A41
004011A5 |. POPAD --> restaure les registres sauvés par le PUSHAD
004011A6 |. JNZ SHORT 004011B2 --> saute si EAX n'est pas égal à ED3C9A41

004011A8 |. CALL 004011B3
004011AD |. MOV EAX,29A --> met EAX à 29A pour avoir le droit au good boy.
004011B2 \> RETN --> sort du CALL

A partir du serial donné, deux routines (la jaune et orange) calculent un nombre qui sera placé dans EAX si ce nombre est égal à ED3C9A41 alors le serial est bon. Si c'est le cas on ira en 4011B3 grâce au CALL. Je ne vais pas expliquer en détail ce qu'il y fait car c'est inutile, mais ce CALL va transformer le faux mdp qu'on peut voir dans les strings en un bon mdp grâce au serial valide. On voit effectivement :

004011B4 |. BF 44304000 MOV EDI,crackme.00403044 ; ASCII "The Solution perhaps is 'AzulGranaEsMiColor'"
004011B9 |. BE 08304000 MOV ESI,crackme.00403008 ; ASCII "12345"

Et à la suite de ça des opérations entre EDI et ESI (plus ou moins directement) qui ont pour effet de modifier le contenu de EDI (avec des XOR) et donc par conséquent le mdp. Bon mais cette partie est indispensable donc on y touchera pas. On va revenir s'intéresser à la comparaison de EAX et ED3C9A41. Comment à partir des deux routines pourrait-on trouver le bon serial. Si on veut faire ça de tête c'est impossible. Il va donc falloir tester toutes les possibilités jusqu'à trouver la bonne. Si on considère que le serial est composé de chiffres on a 100.000 possibilités, or le crackme nous indique d'entrer un "keyword" donc il se pourrait bien que ce soit un mot. Ce qui fait, si je compte bien, environ 12 millions de keyword différents possibles pour des lettres uniquement en majuscules OU en minuscules et 380 millions pour un mélange des deux. Inutile donc de vous dire que les tester à la main n'est pas la bonne solution. Ou alors commencez maintenant vous gagnerez du temps ! Si on continue dans les calculs d'un niveau d'utilité inversement proportionnel aux nombres trouvés, connaissant le serial je peux vous dire que vous avez pour 15-20 ans non-stop avant de le trouver. ;-) Bon trêve de plaisanterie et passons aux choses sérieuses.

3° Programmation du Brute-Force

Tout d'abord il faut réfléchir sur comment on va s'y prendre pour lui faire tester toutes les possibilités sans en oublier une. Vous savez compter au moins jusqu'à 10 ? Très bien dans ce cas vous êtes l'homme (ou la femme) de la situation. On va faire comme si le programme comptait mais ici ça sera avec des lettres. On lui rentrera le keyword de valeur la plus basse (correspondant à 00000 si on travaillait avec des chiffres) et ensuite on va incrémenter le 1er rang de 1 à chaque fois, une fois la valeur de celle-ci arrivée à [la valeur la plus haute + 1] on remet la valeur de celle-ci à zéro (enfin à la valeur la plus basse) et on incrémente le 2ème rang...etc... Si vous ne m'avez pas compris c'est soit car j'explique mal ou soit parce que vous m'avez menti et que vous ne savez pas compter. Voici un exemple parlant : on commence avec aaaaa, puis on a aaaab -> aaaac -> ... -> aaaaz -> aaaba -> ...etc... On va donc obtenir des boucles de tests dans des boucles pour tester si chaque rang ne dépasse pas la valeur autorisée. Bon maintenant qu'on sait ce qu'on va faire à peu près (il n'y a plus qu'à mettre en forme tout cela) il va falloir en premier lieu regarder où on va pouvoir mettre ça. Car ça c'est facile donc vite fait. :-)

En descendant un peu dans le code on se rend compte qu'il y a une énorme quantité de bytes libres. On va donc se placer ici. Allez hop on va s'installer en 40122A. Mais vous vous doutez bien que le programme ne va pas se dire "tiens y a de nouvelles instructions dans le fond là bas je vais voir ce que c'est", non il va falloir lui dire de venir voir ici. Pour cela on va retourner voir juste avant le début de la vérification avec les 2 routines :

0040114B |. 68 08304000 PUSH crackme.00403008 ; /String = "12345"
00401150 |. E8 CB000000 CALL <JMP.&kernel32.lstrlenA> ; \lstrlenA
00401155 |. 83F8 05 CMP EAX,5
00401158 |. 74 13 JE SHORT crackme.0040116D
0040115A |. 83C0 30 ADD EAX,30
0040115D |. 52 PUSH EDX
0040115E |. BA ED304000 MOV EDX,crackme.004030ED ; ASCII "The Keyword is not n characters long"
00401163 |. 8842 13 MOV BYTE PTR DS:[EDX+13],AL
00401166 |. B8 4D010000 MOV EAX,14D
0040116B |. 5A POP EDX
0040116C |. C3 RETN
0040116D |> 60 PUSHAD

La vérification de la taille du serial ne nous sert pas tellement vu que l'on sait maintenant que le serial fait 5 caractères. Mais bon par souci de bien faire on va essayer de faire un truc qui sera un minimum propre tout de même. On va donc emmener le JE 0040116D en 40122A. Le problème c'est qu'il s'agissait d'un saut court et là il serait remplacé en saut long prenant donc 4 bytes de plus ce qui écraserait les instructions situées au dessous. Tant pis on aura le message d'erreur avec un "n" au lieu du nombre de lettres, ce qui n'est pas dramatique. Pour garder ce l'apparition de message il va falloir garder le MOV EAX,14D (car c'est avec cette valeur que le message est affiché) et le RETN uniquement. Donc soit on noppe ce qu'il y a d'inutile entre les 2 ou alors on réécrit à la suite du JE ces deux instructions. J'opte pour la seconde solution mais les deux marchent. Voilà ce que ça nous donne :

00401155 |. CMP EAX,5
00401158 |. JE crackme.0040122A
0040115E |. MOV EAX,14D
00401163 |. RETN
00401164 |. NOP
00401165 |. NOP
00401166 |. MOV EAX,14D
0040116B |. POP EDX
0040116C |. RETN
0040116D |> PUSHAD

Les lignes en jaune ne seront jamais effectuées car à la suite de nos instructions le programme sort du CALL. Et si EAX=5 on saute et on reviendra en 40116D sur le PUSHAD pour la vérification du serial. On retient donc cette adresse, ne vous en faites pas je le fais pour vous sur ce coup là. ;-)

Passons aux choses sérieuses ! On va mettre en forme plus précisément notre algorithme. Voilà comment on va s'y prendre :

- Incrémenter r1 (le 1er rang)
- Comparer r1 à la plus haute valeur possible + 1 (par exemple si on fait les lettres de A à Z, on va comparer r1 à au caractère suivant le Z)
- Si différent alors on sort
- Sinon incrémenter r2
- Réinitialiser r1
- Comparer r2 à la plus haute valeur possible + 1
- Si différent alors on sort
- Sinon incrémenter r3
- Réinitialiser r2
- ...etc... jusqu'à r5

Il faut maintenant trouver l'adresse de chaque rang. Ce n'est pas dur car on sait que notre serial est placé en 403008 donc le 1er rang (ou dernier selon le sens où l'on commence, ça revient au même) est en 403008, le second en 403009, puis 40300A, 40300B et enfin 40300C. Reste à savoir maintenant ce qu'on va mettre comme valeur correspondant aux caractères. Disons qu'on va prendre les majuscules et minuscules (bon je triche car je connais la réponse :-p mais au départ je vous conseille plutôt de commencer avec seulement l'un ou l'autre ça ramera moins, et si les deux ne fonctionnent pas testez avec les deux). On regarde donc dans une table ascii (incluse dans HexDecCharEditor avec le bouton avec un carré jaune) les valeurs correspondantes : en hexa 'A' à 'Z' cela fait de 41 à 5A et 'a' à 'z' de 61 à 7A. On ne va donc pas s'embêter pour 8 valeurs entre les deux séries et on va donc les inclure. Ce qui nous donne de 41 à 7A.

Avec tout ce qu'on a, on va pouvoir commencer à rentrer les instructions en 40122A.

INC BYTE [403008] --> r1+1, "byte" indique qu'il faut incrémenter uniquement un octet (un rang)
CMP BYTE [403008],7B --> compare r1 à la plus grande valeur + 1 c'est à dire à 7A+1=7B
JNZ 0040116D --> retourne au PUSHAD pour vérifier le serial
INC BYTE [403009] --> r2+1
MOV BYTE [403008],41 --> initialise r1 en mettant sa valeur à 41 (caractère 'A')
CMP BYTE [403009],7B
JNZ 0040116D
INC BYTE [40300A] --> r3+1
MOV BYTE [403009],41
CMP BYTE [40300A],7B
JNZ 0040116D
INC BYTE [40300B] --> r4+1
MOV BYTE [40300A],41
CMP BYTE [40300B],7B
JNZ 0040116D
INC BYTE [40300C] --> r5+1
MOV BYTE [40300B],41
CMP BYTE [40300C],7B
JNZ 0040116D
RETN --> sort de la routine pour revenir à l'affichage des messagebox, si on arrive ici c'est qu'aucun code testé ne marche.

Il ne reste plus qu'un élément à rajouter. Vous ne voyez pas ce qu'il faut ? Une fois le serial incrémenté il va vérifier le serial et s'il est invalide il va faire quoi ? Et bien nous emmener au mauvais message. On va donc modifier ce saut pour l'emmener en cas d'échec sur le début de notre routine pour incrémenter notre serial. Le saut à modifier est le suivant :

004011A0 . CMP EAX,ED3C9A41
004011A5 . POPAD
004011A6 . JNZ SHORT crackme.004011B2
004011A8 . CALL crackme.004011B3
004011AD . MOV EAX,29A
004011B2 > RETN

On va donc remplacer la destination du saut par 40122A. Mais là on a un problème ! Le saut est plus long que prévu et on a donc pas assez de bytes pour écrire le saut. On va donc procéder à un petit déménagement des instructions qui suivent le JNZ. On les déplace en 4012AA là où il y a de la place. Le JNZ reste ici mais en changeant la destination vers 40122A comme on l'avait prévu. En revanche à la place du CALL (enfin à la suite du JNZ) on va mettre un JMP vers 40122AA. J'espère que vous arrivez à suivre un peu. Voici plus clairement ce qu'on change :

004011A0 . CMP EAX,ED3C9A41
004011A5 . POPAD
004011A6 . JNZ crackme.0040122A
004011AC . JMP crackme.004012AA
004011B1 . NOP
004011B2 . RETN
004011B3 . PUSHAD

Les instructions en bleu ne seront jamais effectuées. Et en 4012AA on retrouve :

004012AA CALL crackme.004011B3
004012AF MOV EAX,29A
004012B4 RETN

Tout ça étant désormais en place il va falloir désormais tester notre crackme modifié. Je vous conseille, au lieu de lancer le crackme directement, de tester cela dans OllyDbg en posant des BP à certains moments. Et n'oubliez pas de sauvegarder votre fichier maintenant car si vous avez fait des erreurs en tapant les instructions le programme pourrait par exemple tourner indéfiniment en rond ce qui le ferait planter et vous ferait perdre vos modifications.

Comme vous l'avez vu on n'a pas initialisé le serial au départ donc c'est à nous de le faire en tapant le serial au départ. Ainsi pour que le crackme teste toutes les lettres il faut taper "AAAAA". En fait avec cela la solution "AAAAA" ne sera pas testée car elle sera incrémentée avant la vérification donc si on veut vraiment tester cette solution on le fait à la main ou on commence par "@AAAA" le '@' correspondant à 40h soit 1 de moins que pour le 'A'.

Pour le tester on va par exemple breaker sur l'incrémentation de r1 et faire ensuite F9 à plusieurs reprises pour qu'il fasse plusieurs tours. Vous vérifierez bien (dans la fenêtre du bas à l'adresse 403008) que ce rang là est bien augmenté et que le passage à la "dizaine" supérieur se déroule comme prévu. Ça fonctionne ? Très bien. Dans ce cas le brute force est opérationnel. Cependant si vous lancez le programme tout seul il risque au mieux de ramer un peu et au pire de planter car il doit tester un nombre important de possibilités. Alors vous pouvez éventuellement poser un BP sur l'incrémentation du dernier rang, ça permettra au programme de respirer un peu :-p. Il suffira d'appuyer sur F9 jusqu'à ce qu'on trouve le bon serial (au maximum 50-60 appuis). Enfin libre à vous de tester comme vous le souhaitez. ;-)

On trouve donc à un moment un beau messagebox avec marqué "Congratulations - The solution perhaps is 'PobreLorito'" ! On a donc réussi ! Si vous avez lancé le bruteforce avec OllyDbg pour connaître le keyword qui fonctionnait il vous suffit de regarder à l'adresse 403008 où l'on peut voir "ChaOs". Comme dirait l'autre : "another crackme cracked" ! :-p

Conclusion

Voilà un crackme cracké d'une manière différente de ce qu'on a pu voir dans mes autres cours. La méthode que j'ai utilisée n'est pas forcément la meilleure ou la plus rapide mais elle a au moins le mérite de fonctionner ;-). Comme quoi il est possible de brute forcer sans savoir vraiment programmer. Il faut juste savoir exploiter au maximum les connaissances que l'on a pour résoudre des problèmes.

J'espère que ce cours vous a plu. N'hésitez pas à faire des remarques (critiques, suggestions, autre méthodes...) par mail : deamon.crack (at) netcourrier.com ou bien sur le forum : http://www.forumcrack.new.fr/ . Excusez-moi pour cette fin de cours qui est peut-être un peu confuse car j'étais un peu pris par le temps et de plus j'ai mal géré pour le coup du saut court qui se transforme en saut long. Mais maintenant que vous avez compris vous devriez pouvoir refaire ce crackme en l'améliorant tout cela. :-)

Deamon, le 15-18 février 2005


Site de la TeaM xtx

Apprendre la programmation et le Reverse Engineering Vu dans... http://xtx.free.fr/

  • Home
  • Tutoriels
  • xtx Cracking Progressif
  • Défis
    • Défis Coolmen
    • Défis Kirjo
    • Défis Sp0ke
    • Défis papabango
    • Défis hibou28
    • Défis Meat++
    • Défis mars
    • Défis Z!PPer
    • Défis Taloche
    • Défis Sorcier21
    • Défis S_O_D
    • Défis SpiNa
    • Défis burner
    • Défis horgh
  • Templates
  • Outils
    • Décompilateurs
    • Débogueurs
    • Détecteurs
  • Forum

Salut la compagnie !

by xtx on juil.21, 2014, under Non classé

Ça fait un moment que l’idée de créer un site avec une vrai base de données MySQL me démangeait !

Voici donc le nouveau site de la Team xtx, avec son nouveau look !

RAPPEL : Deux adresses pour le forum :

http://xtx.yzi.me

RAPPEL : Deux adresses pour le forum :

http://xtx.zz.mu

Sp0ke.

No tags 11 Comments more...

RSS Feed

  • Articles récents

    • Salut la compagnie !

  • Commentaires récents

    • Coolmen dans Salut la compagnie !
    • Coolmen dans Salut la compagnie !
    • taloche dans Salut la compagnie !
    • Coolmen dans Salut la compagnie !
    • xtx dans Salut la compagnie !

  • Archives

    • juillet 2014

  • Catégories

    • Non classé

  • Méta

    • Inscription
    • Connexion
    • Flux RSS des articles
    • RSS des commentaires
    • Site de WordPress-FR

    • Recent Posts

    • Salut la compagnie !

    Archives

    • juillet 2014

    Tags

      Copyright © 2014-2015 Site de la TeaM xtx .customised by Sp0ke All rights reserved.
      Jarrah theme by Templates Next | Powered by WordPress


---------------------------------------------- SOMMAIRE ----------------------------------------------

Chapitre I - Les bases

Notions de bases : Le processeur et la mémoire
Assembleur : Qu'est-ce que l'assembleur, premières instructions
Ollydbg : Présentation, prise en main
Mon premier crackme partie 1 : Recherche d'une string, remonter un saut, nopper une instruction
Mon premier crackme partie 2 : Retrouver une adresse, breakpoints, pas à pas, lecture de la mémoire
Crackme2 partie 1 : les APIs Window, list of windows (les contrôles), les labels dans le dump, les BP memory
Crackme2 partie 2 : gérer les BP, l'Execute till return, les labels dans le code, remonter un call

Chapitre II - Quelques principes

Pré-analyse : Peid, RDG Packer Detector, Protection ID
Le nagscreen : le principe des fenêtres, enregistrer nos modifications
La limite de temps : les boites de dialogue, inverser un saut, les constantes, modifier une instruction
Enregistrement via la base de registre (à venir)
Enregistrement via un fichier licence (Keyfile) (à venir)
NetCheck (à faire)
Dongle (à faire)

Chapitre III - Les anti-debugs

Les classiques : IsDebuggerPresent, recherche du debugger, rdtsc, … (à faire)
L’obfuscation, le junk code (à faire)

Chapitre IV - Le dotNET

Présentation des outils : Reflector, ILSpy ... (à faire)
Deux techniques d'approche d'une cible .NET
Analyse avec les outils orientés .NET
Analyse sous Olly

Chapitre V - Le Visual Basic

Présentation de SmartCheck (à faire)
Exemple d'utilisation (à faire)
Présentation de VB Decompiler (à faire)
Exemple d'utilisation (à faire)

Chapitre VI - Le Delphi

Présentation de DeDe (à faire)
Exemple d'utilisation (à faire)

Chapitre VII - Hachage

CRC32 (à faire)
MD5 : deux exemples de protection
SHA (à faire)

Chapitre VIII - Cryptographie

Cryptographie symétrique : un exemple de Blowfish
RSA - exemple de chiffrement asymétrique simple

Chapitre IX - Les packers

Le plus simple : UPX
Aspack
Asprotect (à faire)

Chapitre X - Conclusion

Le mot de la fin (à faire)
Les participants

Team XTX / ©2011 / http://xtx.a.nf/


Introduction

Le cracking est une activité dérivée du reverse engineering. Le reverse engineering (ou en français : ingénierie inverse). Ce dernier consiste à désassembler des programmes avec des outils spécialisés afin d'en étudier leur fonctionnement, alors que le cracking consiste à étudier particulièrement les protections de ces programmes afin de les contourner. Il ne faut pas confondre le cracking, comme c'est souvent le cas, avec le hacking qui lui est orienté réseau et internet.

De nombreux tutoriels sur le cracking existent déjà, mais moins nombreux sont ceux qui accompagnent progressivement le débutant des bases vers des sujets plus complexes. Il y a eu, pour les plus anciens, le légendaire Millenium Cracking Tutorial d' AcidBurn, les cours de Deamon, les vidéos de Lena151, etc...

Malheureusement, ceux-ci sont : Soit en anglais, soit dépassés au niveau des outils (Ex: W32Dasm appelé aussi windasm, n'est plus adapté !)

C'est pourquoi les membres du forum XTX ont décidé de créer un ensemble de tutoriels permettant d'aborder, chacun à son rythme, les outils actuels ainsi que les protections récentes.

Les cours sont progressifs et rédigés par différents auteurs. Chacun ayant des techniques personnelles différentes, le résultat proposé est des plus intéressant !

Nous espérons que chacun d'entre vous trouvera de quoi le satisfaire et nous vous souhaitons bonne route dans le monde du cracking.

L'équipe XTX

Mise à jour du 09/07/2013

Team XTX / @2011 / http://xtx.a.nf/


CrackMe1

---------------------------------------- Chapitre I - Les bases ----------------------------------------

1er crackme - partie 2

Reprenons le crackme original dans Olly. Nous avons vu comment arriver dans une boucle qui nous semblait intéressante. Et si maintenant nous tentions de comprendre ce qui se passe ?
Puisque nous connaissons l'adresse du JNE, pas la peine de rechercher manuellement celui-ci. Clic droit dans la fenêtre de code, Go to/Expression (ou CTRL+G) nous ouvre une boîte de dialogue bien pratique :

Il nous suffit de taper l'adresse recherchée (401181) puis OK pour arriver directement au bon endroit. On va sélectionner la ligne juste au-dessus de la boucle et poser un point d'arrêt (breakpoint ou BP) afin que l'exécution du programme s'arrête lorsque le processeur voudra exécuter cette commande. Pour cela il suffit d'utiliser la touche F2 (ou clic droit, Breakpoint/Toggle). L'adresse est surlignée en rouge, le BP est posé. Nous allons en plus écrire un commentaire en face de cette ligne. Utiliser la touche ; (ou clic droit, Add comment ... et dans la fenêtre qui apparaît saisissez votre texte en évitant les accents et les caractères spéciaux (personnellement je fais toujours précéder mes commentaires par => afin de les différencier de ceux d'Olly). Votre ligne doit maintenant ressembler à ça :

L'avantage de saisir systématiquement un commentaire en face de vos BP, c'est, lorsque vous en aurez posé plusieurs, de pouvoir les identifier facilement dans la fenêtre les listant, accessible par le bouton Alt+B (ou Alt+B) :

Maintenant lançons notre crackme avec F9 (ou F9). Saisissez un nom, un serial, et cliquez sur OK pour que Olly breake (suspend l'exécution du programme). Vous pouvez voir en rouge dans la fenêtre de Olly les valeurs qui ont changées (registres et pile). Nous allons maintenant tracer en pas à pas, c'est à dire exécuter les instructions les unes après les autres en regardant ce qui se passe. Servez vous de la touche F8 (ou F8). Au premier appui, certaines valeurs de registres changent, elles apparaissent en rouge. Ici sont concernés les registres ESI, EIP ainsi que le EFLAG, et plus particulièrement le flag (drapeau) T :

La première chose que l'on remarque, c'est notre serial bidon dans ESI. En fait ce n'est pas réellement notre serial, mais l'adresse où il est stocké. Olly nous rajoute, à coté, le contenu de la mémoire pointé par ESI (dans la mesure où c'est de l'ASCII). Regardons de plus près le code en cours :

MOV AL, BYTE PTR DS:[ESI]

L'instruction MOV indique un déplacement de données, AL indique le registre de destination, BYTE PTR est la taille du registre de destination ( BYTE = 8 bits soit par exemple AH ou AL, WORD = 16 bits soit par exemple AX, DWORD = 32 bits soit par exemple EAX), DS nous indique qu'il s'agit de données contenues dans le segment DATA et ESI contient l'adresse dans le segment DATA où se trouve exactement ces données. On va donc voir apparaître dans AL, qui est une partie d'EAX, la valeur stockée en 40308C. Dans la fenêtre d'informations, Olly nous donne plusieurs indications sur la ligne de code en cours :

[0040308C] = 31 ("1") => entre crochet l'adresse représentée par ESI, ensuite la valeur contenue à cette adresse (en hexadécimal) puis, entre parenthèses, la valeur convertie en caractères ASCII (les caractères classiques).

AL = 03 => nous indique la valeur actuelle du registre AL.

Jump from 401187 => un saut en 401187 nous amène à cette ligne.

Loop 00401170 : loop variables ESI(+1), EDI(+1) => on est dans une boucle (loop) dont les variables ESI et EDI sont incrémentées à chaque tour.

Nous aurions pu aussi, d'une autre manière, voir le contenu de la mémoire à l'adresse concernée : sur la ligne de code, clic droit, Follow in Dump / Memory address, sur le registre ESI ou sur la ligne d'information " [0040308C] = 31 ("1")" clic droit, Follow in Dump ce qui nous positionne le dump (fenêtre mémoire), à l'endroit recherché :

On peut ainsi lire le contenu complet.

Tout cela nous permet de voir que l'on va bien transférer dans AL le premier caractère de notre serial bidon. Appuyons une fois de plus sur F8 (ou F8) et passons à la ligne suivante :

CMP BYTE PTR DS:[EDI],AL

Bon, pas la peine de vous détailler toute la ligne, je pense que vous avez compris qu'ici on compare notre caractère contenu dans AL au 1er caractère contenu dans ESI. On regarde donc ce qu'il y a dans ESI et on voit la chaine : 1FF. Vu que notre serial bidon est comparé à cette chaine, on peut supposer qu'il s'agit du bon serial. F9 (ou F9) pour relancer le prog, on ferme la boite de dialogue mauvais serial, on saisit comme serial 1FF, OK, Olly breake de nouveau. Un petit coup de F8 (ou F8), cette fois-ci, ESI et EDI sont égaux, ça a l'air bon. Vous pouvez vous amuser à tracer toute la boucle pour voir les différentes comparaisons sinon F9 (ou F9) et voilà :

Cette fois-ci, vous avez résolu le crackme en utilisant la technique du fishing (trouver le bon serial en analysant le déroulement du programme).

Ce crakme est extrêmement simple, il est évident que vous trouverez trés peu, voir aucun, programme commercial utilisant une protection aussi basique (quoique ...).

Par contre, il vous aura permis de manipuler Olly et de comprendre un peu mieux son fonctionnement.

Continuons à approfondir nos connaissances ...

Kirjo / @2011 / http://xtx.a.nf/


CrackMe2 Part 1

---------------------------------------- Chapitre I - Les bases ----------------------------------------

Crackme2 - partie 1

Cible : CrackMe2

Outils nécessaires :
OllyDbg (by Oleh Yuschuk)

Passons maintenant au Crackme2, disponible dans le dossier "Pack / Chapitre I / 4 - Crackme2 / CrackMe2.rar". Comme le 1er, il a été écrit en assembleur afin de simplifier le code. Ouvrez le, tapez un nom et un serial bidon, OK et on a le message d'erreur :

On ouvre Olly, menu File / Open (ou F3) et on sélectionne le Crackme2. Comme vu précédemment, on va chercher notre string "Serial incorrect". Clic droit dans le code, Search for / All referenced strings et là rien ne se passe ! Rien, excepté un surlignage jaune assez rapide en bas de la fenêtre de Olly :

On a quand même le temps de lire "No items found" (Aucun résultat trouvé) avant que la barre d'information ne revienne à sa couleur normale et affiche de nouveau "Entry point of main module". Cela signifie qu'il n'y a aucune chaîne de caractères ASCII reconnaissable dans notre programme. Comment est-ce possible alors que l'on a le message " Serial incorrect !" qui s'affiche ? Tout simplement car lors de l'écriture du programme il a été prévu de les masquer pour éviter, par exemple, de donner des pistes aux reversers. Il existe de nombreuses techniques pour y parvenir (encodage, cryptage, chaînes dans un fichier annexe, etc). Pour vous donner un exemple simple, prenons par exemple le caractère A. Sa valeur hexadécimale dans la table ASCII est 41h. Imaginons que je stocke 140h, valeur se trouvant en dehors de la table ASCII étendue. Lors de la recherche de strings, Olly ne verra pas cette valeur comme un caractère lisible dans une chaîne et donc l'ignorera. Maintenant, lors de la création d'une string où il faut un A, je demande au programme de prendre cette valeur et de lui soustraire FFh. J'aurais donc 140h - FFh = 41h soit la valeur de mon A. En faisant le même genre de manipulation pour tous les caractères nécessaires, le tour est joué, il n'y a plus de strings visibles !

Il nous faut donc un autre moyen pour trouver où poser un BP intéressant. Pour cela, on va utiliser les APIs de Windows.

  • Que sont les APIs ?

Ce sont des fonctions permettant d'exploiter Windows de façon simplifiée. Lors de l'écriture d'un programme, on fait appel aux APIs en leur passant simplement quelques paramètres pour effectuer différentes actions : accès à la base de registre, lecture/écriture de fichiers, affichage de boites de dialogue, etc. Leurs noms indiquent de façon assez explicite leurs rôles : GetWindowText, MessageBox, CreateFile, RegCreateKey, etc. Elles sont regroupées par type dans des fichiers de type DLL (Dynamic Link Library) tel que : User32.dll, Kernel32.dll, etc. Vous trouverez les différentes APIs et leurs fonctions sur la MSDN (MicroS oft Developer Network platforms) et une liste des principales dans le fichier d'aide Ollydbg.hlp fourni dans le pack (Pack / Chapitre 01 / 1 - Aide Ollydbg).

Revenons à notre crackme. Comment les APIs vont-elles pouvoir nous aider ? Dans le code, faites un clic droit, Search for / Names ou, plus rapide, utilisez le raccourci CTRL+N. Dans la nouvelle fenêtre qui s'ouvre sont listées toutes les APIs utilisées par le programme. En regardant leurs noms, on repère vite quelque chose d'intéressant : GetDlgItemTextA :

C'est la fonction qui permet de récupérer du texte dans une boite de dialogue. Sélectionnez la, clic droit, Find references (ou CTRL+R) et on a encore une nouvelle fenêtre. Dans celle-ci, nous pouvons voir toutes les adresses dans notre programme où notre API est appelée :

La troisième ligne étant l'appel direct vers la DLL, on va se concentrer sur les deux premières. En commentaires, Olly nous ajoute gentiment l'ID (IDentifiant) du contrôle dans lequel va être récupéré le texte. On peut donc vérifier dans la fenêtre windows. Lancez le programme avec F9 (ou F9) puis cliquez sur Alt+W :

List of windows

Si vous n'avez pas la même présentation que moi, faites un clic droit dans une partie vide de la fenêtre, Sort by et sélectionnez Parent. De cette manière, tous les contrôles seront rattachés à la fenêtre dont ils dépendent (fenêtre parent). On retrouve nos 2 ID (1002 et 1003) correspondants à des contrôles Edit (zone de texte) dans la fenêtre "XTX : Cracking Progressif - Crackme2". Et nous avons bien 2 zones de textes dans notre crackme : Nom et Serial. On revient dans la fenêtre References ( Alt + R) et on pose un BP avec F2 sur les deux premières lignes. On saisit un couple name/serial (Kirjo / 12345), OK et Olly breake. Un petit F8 (ou F8) et nous avons ceci :

La string récupérée par la fonction est apparue. Il s'agit ce coup-ci du nom, dans le contrôle ID1002 et on peut voir dans la pile qu'il est stocké à l'adresse 403350. F9 ( ou F9), on breake sur le 2eme BP, F8 (ou F8) pour exécuter la fonction :

On a maintenant le serial, récupéré dans le contrôle ID1003, stocké à l'adresse 403170. Positionnez-vous dans le dump, faites un clic droit, Goto / Expression (ou CTRL + G). Dans la boîte de dialogue, saisissez dans la case "Enter address expression" l'adresse du serial : 403170 puis OK. Le dump se positionne sur l'adresse saisie :

On va utiliser un petit truc pour faciliter la lecture du code. Faites un clic sur le début du serial (31 ou le 1 dans la colonne ASCII) pour le sélectionner. Taper ":" (ou clic droit / Add label...). Dans la boite de dialogue, tapez "serial" puis OK. Avant cette petite manipulation, les lignes dans le code faisant référence à cette adresse apparaissaient comme ça :

Dorénavant, on lira ceci :

Au même endroit, on va maintenant poser un Break Point Memory (BPM). Ce sont des points d'arrêt que l'on peut poser sur des emplacements mémoire. On peut choisir si Olly breakera sur Read access (lecture du contenu de l'emplacement), Write access (écriture de données sur cet emplacement) ou Execution. Faites un clic droit sur le 31, Breakpoint / Memory ..., puis dans la boite de dialogue cochez Read Access. Chaque fois que le serial sera lu, Olly breakera et on pourra regarder ce que le programme en fait (comparaison avec le bon serial par exemple ...). On relance avec F9 ( ou F9) et on breake ici :

Ici, nous sommes dans le module KERNELBASE, aucun intérêt pour nous. Encore F9 (ou F9) et cette fois ci on revient dans notre programme :

Modification du serial

En traçant avec F8 (ou F8) on analyse facilement ce qui se passe dans cette boucle (voir les commentaires). A la fin de celle-ci, on voit dans le dump notre serial modifié mais ce n'est donc pas ici qu'il est comparé. Notre BPM étant toujours actif et le serial modifié étant à la place du serial original, on continue avec F9 (ou F9). Olly breake à nouveau :

Voilà un endroit beaucoup plus intéressant ! Première chose que l'on remarque, c'est que l'adresse de notre serial modifié est chargée dans ESI (1). On analyse ensuite en traçant avec F8 (ou F8) la boucle qui suit (2). On voit que notre serial modifié est comparé à la string dont l'adresse est indiquée par EDI (3), soit "JBFE" et si la comparaison n'est pas bonne, on sort de la boucle. Solution la plus simple : on remplace le JNE par des NOPs comme on l'avait fait pour le crackme précédent. Solution plus élégante : on essaye de trouver le bon serial à saisir. C'est ici relativement simple.

On a vu que le serial était modifié par un simple XOR 0Fh sur chaque caractère. Cette opération marchant dans les 2 sens (si 45h XOR 0Fh = 4A h alors 4Ah XOR 0Fh = 45h), il suffit de l'appliquer sur les caractères du bon serial pour trouver celui à saisir. Il suffit de regarder dans le dump (4) pour avoir les valeurs ASCII de chaque caractère ce qui nous donne :

  • 4Ah XOR 0Fh = 45h = E
  • 42h XOR 0Fh = 4Dh = M
  • 46h XOR 0Fh = 49h = I
  • 45h XOR 0Fh = 4Ah = J

Saisissez notre nouveau couple name/serial ("Kirjo" / "EMIJ"), OK et :

Voilà, on a utilisé deux techniques : le patching et le fishing. Mais il existe une 3eme solution ...

Kirjo / @2011 / http://xtx.a.nf/


CrackMe2 Part 2

---------------------------------------- Chapitre I - Les bases ----------------------------------------

Crackme2 - partie 2

Cible : CrackMe2

Outils nécessaires :
OllyDbg (by Oleh Yuschuk)

Cette 3eme solution est le keygenning. C'est l'art de créer un programme (keygen : KEY GEN erator, générateur de clé) permettant de calculer un serial en fonction de différentes données comme un nom par exemple. Pour cela, il faut étudier plus en profondeur le programme cible afin de trouver la méthode de calcul utilisée. Reprenons le crackme2. Revenons au départ avec CTRL+F2 (ou CTRL+F2). On relance le crackme2 avec F9 (ou F9), on rentre le couple bidon "Kirjo" / "12345", OK et on breake sur la 1ere API. Un petit F8 (ou F8) pour exécuter celle-ci et on se retrouve là :

Comme précédemment, on peut voir que le nom est récupéré puis stocké à l'adresse 403150 . L'adresse est visible soit grâce au PUSH qui indique à la fonction où écrire ce qu'elle récupère (1) soit dans la pile puisque l'adresse a été poussée dans celle-ci et qu'Olly nous indique son contenu lorsqu'il s'agit de caractères ASCII (2). Nous allons utiliser à nouveau la technique du BPM (breakpoint memory). Soit vous faites un clic droit dans le dump, puis Goto expression / 403150 soit vous faites un clic droit dans la pile sur l'adresse du name puis Follow in dump. Avant de poser un nouveau BP, on va nettoyer les existants afin de breaker uniquement où cela nous arrange. Cliquer sur ALT+Y (ou ALT+Y) :

Ici sont listés tous les memory breakpoints. On retrouve celui que l'on a posé tout à l'heure avec en 1 son adresse (et le label qui nous permet de l'identifier rapidement), en 2 le type du BP (ici R pour Read / lecture et W pour write / écriture) et en 3 son statut (Active ou Disabled / désactivé). Clic droit sur la ligne, Disable et on est tranquille (vous verrez que dans le dump notre BPM est toujours présent mais apparaît désormais en jaune). On retourne dans la fenêtre code ( ALT+C ou ALT+C). Clic droit sur la 1ere lettre du name (ou sur 4Bh), Add label, "name", encore clic droit au même endroit, Breakpoint, Memory ..., cochez Read access et Write access si ils ne le sont pas déjà, OK. On relance avec F9 (ou F9), on breake sur la 2eme API qui récupère le serial, F9 (ou F9) et on breake dans le module KERNELBASE :

Nous le voyons en haut dans la barre de titre. Nous avons breaké ici sur un accès au name (en bas à droite de la fenêtre). Pour revenir dans notre programme juste après le call qui a appelé cette fonction, nous allons utiliser Execute till return, CTRL+F9 (ou CTRL+F9). Cela va nous permettre d'exécuter le code jusqu'à la ligne suivant le prochain RET (rappelez-vous la configuration d'Olly au début de ce tutorial) sans passer par toutes les lignes avec un F8. Un seul nous suffira cette fois-ci pour revenir dans le code du crackme (surveillez la barre de titre) :

Return from Kernel

Juste au-dessus de la ligne où nous sommes revenu, il y a un appel à la fonction lstrlenA (lSTR ingLENgthA / longueur de chaîne) avec comme argument le nom. Pas besoin d'explication sur cette fonction, d'autant qu'Olly nous indique la valeur de retour et où elle est stockée (EAX ). Vu que le code commence par travailler sur le nom, il est possible que l'on soit dans la routine qui calcule le bon serial. On va donc se mettre un repère (je vous montre cette astuce car même si elle n'est pas très utile ici, elle pourra vous servir lorsque vous tracerez des codes plus compliqué que celui-ci). Au début de la 1ere ligne de la boucle (4011130), vous pouvez voir le signe $ . Cela signifie que cette ligne est appelée par un CALL. Comme on avait fait dans le dump, on va poser un label sur cette ligne. Clic droit, Add label et saisissez "CalculSerial". Une fois cela fait, mettez-vous dans la fenêtre information sur la ligne Local call from ..., clic droit et vous pouvez voir d'où est effectué l'appel :

Si vous cliquez sur Goto call ..., vous arriverez directement sur la ligne d'appel :

Cette technique permet de remonter le code en suivant les calls. D'autre part, vous pouvez voir la nouvelle écriture du call vers notre routine. Cette parenthèse refermée, traçons ligne par ligne avec F8 (ou F8) et mettons en commentaire ce que fait le code (les commentaires sont accessibles soit avec clic droit / Add comment soit avec la touche ";") :

Test du nom

Dans cette partie, il est facile de se rendre compte que le programme teste si un nom a été saisi puis vérifie si le nombre de caractères est inférieur à 20d (14 h).

XOR du nom

Ici, de la même manière que pour le serial saisi, on modifie le nom en effectuant un XOR 13h (19 d) sur chaque caractère. On arrive ensuite sur la boucle que l'on a déjà étudiée (celle qui modifie le serial saisi), on va donc la passer rapidement en posant un BP (F2) à la sortie de celle-ci sur le POP EAX :

BP sur POP EAX

F9 (ou F9) et on étudie la suite à coup de F8 (ou F8) :

Follow in dump

Pour les 2 premières lignes, pas de problème, par contre la 3eme met dans EDI une adresse mémoire. On va aller voir ce qu'elle pourrait bien contenir. Faites un clic droit sur la ligne de code, Follow in Dump / Immediate constant. Le dump se positionne à l'adresse contenue dans la ligne de code et nous pouvons voir qu'il s'agit d'un emplacement vide, sans doute l'endroit où va être stocké le résultat des prochains calculs. Continuons notre analyse :

Le code (1) ne présente pas de difficultés, par contre en 40119A, on récupère en mémoire une valeur que l'on charge dans DL. Pour savoir de quoi il s'agit, cliquez dans la fenêtre informations sur la ligne contenant l'adresse (2) puis clic droit / Follow in Dump (3). Le dump se positionne, on remonte d'une ligne pour une meilleure vue (4) et on voit qu'il s'agit du 5eme caractère (soit le dernier) du nom modifié (5). Continuons l'analyse avec notre amie F8 (ou F8) :

On passe ensuite à la comparaison du serial saisi et du serial qui vient d'être calculé. On a maintenant tous les éléments pour coder un keygen. Pour résumer :

  • On vérifie que la longueur du nom soit inférieure à 14h (20d).
  • On effectue un XOR 13h (19d) sur la valeur hexadécimale de chaque caractère.
  • On additionne la valeur de chaque caractère de la chaîne ainsi obtenue + la position de chaque caractère.
  • On prend chaque bit de ce résultat et on ajoute ou on soustrait 0Ah ( 10d) jusqu'à n'avoir que des valeurs comprises entre 41h (A) et 5Ah (Z).
  • On effectue un XOR 0Fh (15d) sur chacune de ces valeurs ce qui nous donne le serial à saisir.

Plus qu'à traduire cela dans votre langage préféré ...

Maintenant que vous avez acquis les bases du reversing, nous allons voir quelques exemples de comportement d'un programme à objectif commercial.

Kirjo / @2011 / http://xtx.a.nf/


L'assembleur

---------------------------------------- Chapitre I - Les bases ----------------------------------------

L'assembleur

L'assembleur (ou ASM) est le langage informatique le plus proche de la machine. C'est en asm que les compilateurs convertissent les programmes écrits en Visual basic, C/C++, Delphi, DotNet, etc. On parle de langage de bas niveau pour l'ASM et de haut niveau pour les autres.

Basée sur un codage en valeur hexadécimale (base 16), chaque instruction de l'asm peut être représentée soit par sa valeur (on parle alors de l'opcode) ou sous forme de mot clé, beaucoup plus compréhensible pour nous. Par exemple pour un saut : EBh = JMP (le h indique une valeur en hexadécimal, un d indiquera une valeur en décimal).

Nous n'allons pas voir toutes les instructions (il y en a pas mal !), mais les principales qui nous servirons dans les premiers cas que nous allons étudier. Vous découvrirez les autres au fur et à mesure de votre évolution.

  • Les transferts de données :

MOV (Move)
Syntaxe : MOV Destination, Source
Déplace les données entre registres et mémoires. Le contenu de la source est copié dans la destination sans être changé.

PUSH (Push Word onto Stack)
Syntaxe : PUSH Source
Place sur le haut de la pile les données récupérées dans la source de 16 ou 32 bits (mémoire ou registre).

POP (Pop Word off Stack)
Syntaxe : POP Destination
Permet de restaurer des données stockées sur le dessus de la pile (il s'agit d'un dépilage). La destination est une zone mémoire ou un registre de 16 ou 32 bits.

  • Les instructions arithmétiques :

ADD (arithmetic ADDition)
Syntaxe : ADD Destination, Source
Effectue une addition sans retenue. Le contenu de la source est ajouté à la destination, le résultat étant dans la destination.

SUB (SUBtract)
Syntaxe : SUB Destination, Source
Retranche la source de la destination et stocke le résultat dans la destination. Contrairement à SBB, cette instruction ne prend pas en compte l'indicateur de retenue [CF]. Cette instruction permet d'effectuer une soustraction ordinaire. La source et la destination doivent être de même taille : 8, 16 ou 32 bits.

MUL (unsigned MULtiply)
Syntaxe : MUL Source
Multiplie le contenu de EAX ou AX ou AL (selon la taille du registre donné en paramètre) par la source, les deux nombres étant considérés non signés. Le résultat est transféré dans la destination qui est le registre EAX ou le couple de registres EDX:EAX. Pour les nombres signés, ce sera l'instruction IMUL.

DIV (DIVide)
Syntaxe : DIV Opérande
Cette instruction permet d'effectuer une division non-signée par l'opérande. Selon la taille de l'opérande, on divise AX, ou DX:AX, ou EDX:EAX. Le reste de la division est alors stocké dans AH, ou dans DX, ou dans EDX. Pour les nombres signés, ce sera l'instruction IDIV.

INC (INCrement)
Syntaxe : INC Destination
Incrémente (augmente de 1) la destination, qui peut être un registre ou une mémoire = variable) de 8, 16 ou 32 bits.

DEC (DECrement)
Syntaxe : DEC Destination
Décrémente de 1 (retranche 1) la destination, qui peut être un registre ou une mémoire (= variable) de 8, 16 ou 32 bits.

  • Les instructions de comparaison :

CMP (CoMPare)
Syntaxe : CMP Destination, Source
La destination est comparée à la source, provoquant une mise à jour des indicateurs. Cela consiste en une soustraction de la source à la destination, sans retenir le résultat, ne faisant que modifier les indicateurs (flags).

  • Les instructions de rupture de séquences :

CALL (Procedure CALL)
Syntaxe : CALL Destination
Sert à appeler des sous-routines, destination étant l'adresse de début de cette sous-routine.

RET (RETurn From procedure)
Syntaxe : RET / RETF
Met fin au sous-programme et retourne à la ligne de code suivant le CALL.

JMP (JuMP)
Syntaxe : JMP Destination
Permet d'effectuer un saut vers la destination spécifiée.

  • Les sauts conditionnels :

JA (Jump if Above) : saut si supérieur
JAE (Jump if Above or Equal) : Saut si supérieur ou égal
JB (Jump if Below) : saut si inférieur
JBE (Jump if Below or Equal) : saut si inférieur ou égal
JE (Jump if Equal) : saut si égal
JG (Jump if Greater) : saut si supérieur
JGE (Jump if Greater or Equal) : saut si supérieur ou égal
JL (Jump if Less) : saut si inférieur
JLE (Jump if Less or Equal) : saut si inférieur ou égal
JZ (Jump if Zero) : saut si égal (zéro)
JNA (Jump if not Above) : saut si pas supérieur
JNAE (Jump if not Above not Equal) : saut si ni supérieur ni égal
JNB (Jump if not Below) : saut si pas inférieur
JNE (Jump if not Equal) : saut si pas égal (zéro)
JNG (Jump if not Greater) : saut si pas supérieur
JNGE (Jump if not Greater not Equal) : saut si ni supérieur ni égal
JNL (Jump if not Less) : saut si pas inférieur
JNLE (Jump if not Less not Equal) : saut si ni inférieur ni égal
JNZ (Jump if not Zero) : saut si pas égal

Pour d'autres instructions ASM, je vous invite à consulter le fichier d'aide Ollydbg.hlp fourni dans le dossier "pack" de ce tutoriel ou à vous rendre sur des sites comme Gladir.com.

Kirjo / @2011 / http://xtx.a.nf/


Notions de base

---------------------------------------- Chapitre I - Les bases ----------------------------------------

Notions de bases

La mémoire

Ce sont des emplacements où sont stockées des données. On peut accéder à ces emplacements, que ce soit en lecture pour récupérer des données ou en écriture pour conserver des données, grâce à une adresse.

La pile (ou stack) est une partie particulière de la mémoire. Elle sert principalement à stocker des données provisoires (résultat intermédiaire d'un calcul, adresse de retour d'une sous-routine, etc). Comme son nom l'indique, on empile des valeurs les unes sur les autres. Retenez cette notion d'empilage car c'est elle qui fait le fonctionnement particulier de la pile. En effet, pour enlever une valeur, il faut d'abord retirer une à une toutes celles qui sont au-dessus, en commençant par la plus récente. Ce traitement des données est appelé LIFO (Last In First Out).

Le processeur

C'est lui qui exécute les instructions données par le programme. Pour ce faire, il possède des mémoires internes qui sont utilisées de manière spécifique et appelées registres. On répartit ceux-ci en 4 groupes : les registres généraux (ou de travail), les registres de segments, les registres d'offset (ou pointeurs) et le registre Eflag (drapeaux ou indicateurs).

  • Les registres généraux :

Ce sont des registres de 32 bits qui servent principalement à stocker des résultats intermédiaires. Ils sont au nombre de 4 :

EAX : Accumulateur
EBX : Registre de base
ECX : Compteur
EDX : Données

Chaque registre peut être utilisé en registres de 16 bits (AX, BX, CX ou DX) qui eux peuvent être divisés en 2 registres de 8 bits ( AH et AL, BH et BL, CH et CL, DH et DL). On parle alors de la partie haute ( H pour hight) ou de la partie basse (L pour low) du registre :

EAX (32 bits)
 | AX (16 bits)
 | AH (8 bits) | AL (8 bits)
  • Les registres de segments:

Pour savoir où retrouver des données, on a besoin d'une adresse. Celle-ci sera composée de 2 parties : le segment et l'offset. Les segments sont au nombre de 6 et permettent de savoir dans quelle zone de la mémoire il faut aller chercher :

CS (Code Segment) : Indique l'adresse de début de code
SS (Stack Segment) : Adresse de la pile
DS (Data Segment) : Adresse des données du programme
ES (Extra Segment) : Segment supplémentaire
FS (extra Segment) : Segment supplémentaire
GS (extra Segment) : Segment supplémentaire

  • Les registres d'offset :

C'est la deuxième partie de l'adresse, celle qui nous indique précisement l'emplacement dans le segment concerné de ce que nous cherchons. Il y en a 5 :

ESI (Extended Source Index) : Utilisé lors de manipulation de suite d'octets, associé à DS
EDI (Extended Destination Index) : Utilisé lors de manipulation de suite d'octets, associé à DS ou ES
EBP (Extended Base Pointer) : Pointe sur une adresse dans la pile, associé à SS
ESP (Extended Stack Pointer) : Contient le déplacement nécessaire pour atteindre le sommet de la pile
EIP (Extended Instruction Pointer) : Indique la prochaine instruction qui va être exé cutée, associéà CS

  • Le registre Eflag :

C'est un registre dont chaque bit donne une indication au processeur, chaque bit ne pouvant avoir qu'une valeur : 0 ou 1. Ceux qui nous intéressent plus particulièrement sont :

CF (Carry Flag) : Positionné à 1 si la dernière opération a généré une retenue (mode non signé), sinon à 0
PF (Parity Flag): Positionné à 1 si dans les 8 bits de poids faible du résultat de la dernière opération, le nombre de bits à 1 est pair, à 0 si ce nombre est impair.

AF (Auxiliary carry Flag) : Positionné à 1 si la dernière opération a généré une retenue du bit numéro 3 vers le bit numéro 4, à 0 sinon.
ZF (Zero Flag) : Positionné à 1 si les deux opérandes utilisés sont égaux, sinon positionné à 0. Son nom vient de la différence nulle entre les deux opérandes.
SF (Sign Flag) : Positionné à 1 si la dernière opération a généré un résultat négatif, à 0 s'il est positif ou nul.
DF (Direction Flag) : Positionné à 1 si le transfert de données se fait en décrémentant les offsets, à 0 sinon (incrémentation des offsets).
OF (Overflow Flag) : Positionné à 1 si le dernier résultat a débordé de la taille du registre, sinon à 0.

Tout cela peut paraître compliqué quand on débute, mais avec la pratique ces notions deviendront rapidement évidentes.

Kirjo / @2011 / http://xtx.a.nf/


Ollydbg

---------------------------------------- Chapitre I - Les bases ----------------------------------------

Ollydbg

OllyDbg est un débogueur pour Windows développé par Oleh Yuschuk et dont l'évolution se poursuit. La version la plus récente est la 2.01 alpha 4, pourtant la v1.10 est la plus utilisée, car on peut y intégrer de nombreux plugins (les plugins sont des petits fichiers complémentaires permettant d'ajouter des fonctionnalités au programme original) alors que la v2.01 alpha 4 vient tout juste d'intégrer cette possibilité, mais au moment où j'écris ces lignes, il ne semble pas y avoir de compatibilité avec les plugins existants.

  • Qu'est ce qu'un débogueur ?

C'est un programme permettant de suivre pas à pas (c'est à dire instruction par instruction) le déroulement d'un autre programme. On peut voir en temps réel (just-in-time) la ligne de code exécutée, la valeur des registres, le contenu de la pile, les valeurs en mémoire et on peut surtout poser des breakpoints (BP). Les breakpoints sont des points d'arrêts que l'on peut poser sur une ligne de code, sur un emplacement mémoire ... et qui feront s'arrêter (breaker) le programme en cours d'exécution lorsqu'il arrivera sur ceux-ci. Je pense que vous voyez déjà l'utilité des breakpoints pour nous !

  • L'installation d'Ollydbg

Je vous conseille de télécharger les 2 versions directement sur le site Ollydbg : http://www.ollydbg.de/
La v1.10 se trouve ici : odbg110
La v2.01 beta 2 ici : odbg201h

Nous utiliserons en règle générale la v2.01.

Ollydbg ne nécessite pas d'installation. Décompressez l'archive dans un dossier, ouvrez celui-ci, cliquez sur ollydbg.exe et c'est parti !

  • Présentation d'Ollydbg

A la première ouverture, une fenêtre vide à part les menus et quelques boutons. Ouvrons (File / Open) un programme exécutable (*.exe), ici le Bloc-notes (C:\WINDOWS\notepad.exe) :

La zone de travail se décompose en 5 parties :

  1. Fenêtre Code
    2.
  2. Fenêtre Registres
    3.
  3. Fenêtre Informations
    4.
  4. Fenêtre Mémoire (ou dump)
    5.
  5. Fenêtre Pile (ou stack)
  • La fenêtre Code :

Elle se décompose en 4 colonnes : les adresses de début d'instructions en hexadécimal (1), les opcodes en hexadécimal (2), le code en asm (3) et les commentaires divers (4).

  • La fenêtre Registres :

On retrouve les registres généraux et d'offsets (1) ainsi que les flags (indicateurs ou drapeaux) (2). On ne s'intéressera pas aux autres pour le moment.

  • La fenêtre Informations :

Elle nous donne des informations immédiates sur la ligne de code sélectionnée (valeurs des opérandes, les différents points d'appels pour une sous-routine, etc).

  • Le dump (ou fenêtre mémoire) :

Elle se compose de 3 colonnes : l'adresse du 1er bit de la ligne, chaque ligne contenant 16 bits (1), le contenu de chaque bits en hexadécimal (2) et le contenu de chaque bit en caractères ASCII (3).

  • Le stack (ou la pile) :

3 colonnes vont nous intéresser : L'adresse dans la pile en hexadécimal (1), le contenu de la pile pointé par l'adresse en (1) composé de 4 octets (2) et divers commentaires notés par OllyDbg (3).

Nous avons ensuite la barre de titre :

Barre de titre

On peut voir le nom du programme en cours de débogage ainsi que le module dans lequel on se trouve (module principal, dll ...).

Puis viennent les menus, tout ce qu'il y a de plus classique, et enfin, la barre de boutons :

Barre de boutons

Boutton Open - F3 : Ouvrir un exécutable à déboguer
Boutton Restart - Ctrl + F2 : Redémarrer le débogage du programme en cours
Bouton close - Alt + F2 : Fermer le programme en cours de débogage
Bouton Run - F9 : Lancer le programme en cours de débogage à partir de l'instruction en cours
Bouton Run Current - F11 : Lancer uniquement le thread affiché (nouveauté v2.01)
Bouton Pause - F12 : Mettre en pause le programme en cours de débogage
Bouton Step Into - F7 : Avancer en mode pas à pas en entrant dans les calls
Bouton Step Over - F8 : Avancer en mode pas à pas sans entrer dans les calls
Bouton Trace Into - Ctrl + F11 : Passer en mode Trace en entrant dans les calls
Bouton Trace Over - Ctrl + F12 : Passer en mode Trace sans entrer dans les calls
Bouton Till Return - Ctrl + F9 : Exécuter le programme jusqu'au prochain Return
Bouton Code User - Alt + F9 : Exécuter le programme jusqu'au retour dans le code du module principal (nouveauté v2.01)
Bouton Log - Alt + L : Afficher la fenêtre des Logs
Bouton Executable - Alt + E : Afficher la fenêtre des programmes en cours d'exécution
Bouton Memory - Alt + M : Afficher la fenêtre de la mémoire
Bouton Window - Alt + W : Afficher la fenêtre des fenêtres actives
Bouton Threads - Alt + T : Afficher la fenêtre des threads
Bouton CPU - Alt + C : Afficher la fenêtre du CPU (Code)
Alt + R - Alt+ R : Afficher la fenêtre des références (résultat des recherches)
Bouton Run Trace - Afficher la fenêtre des logs du Run Trace
Bouton Breakpoint - Alt + B : Afficher la fenêtre des breakpoints
Bouton Memory Breakpoint - Alt + Y : Afficher la fenêtre des memory breakpoints (nouveauté v2.01)
Bouton Hardware Breakpoint - Alt + H : Afficher la fenêtre des hardware breakpoints (nouveauté v2.01)
Bouton Options - Alt + O : Afficher la fenêtre des options

Enfin, dernière indication que l'on peut voir en bas à droite, c'est si le programme débogué est en pause ( Pause ) ou en cours d'exécution ( Running ).

Il ne nous reste plus que quelques réglages à effectuer.

Tout d'abord, rendez-vous dans le répertoire de Ollydbg et créez un nouveau dossier appelé UDD. Les fichiers *.UDD sont des fichiers créés par Olly pour sauvegarder ce que vous faites lors d'une séance de débogage (breakpoints, commentaires, labels, etc) afin de pouvoir tout retrouver lors de la séance suivante. Créer ensuite un deuxième dossier appellé Plugins. Les plugins sont des extensions rajoutant des fonctionnalités à Olly.

Ensuite, récupérez dans le dossier "Pack\Chapitre I\2 - Ollydbg" l'archive win32api.zip et décompressez-la dans le répertoire de votre choix (pour moi ce sera dans D:\WIN32API).
Note : nous verrons plus tard ce que sont les apis windows.

On retourne dans Olly et on clique sur Alt+O et la fenêtre suivante apparaît :

  • Dans la 1ere colonne, cliquez sur Code et vérifiez que la case "Show local module name" soit cochée (cela permettra d'afficher JMP NomProg.00405689 au lieu de JMP 00405689)
  • Sélectionnez ensuite la ligne Debugging et cocher la case "After Execute till return, step over RET", lorsque nous utiliserons CTRL+F9 (ou CTRL+F9), nous arriverons directement à la ligne suivant le prochain RET.
  • Sélectionnez ensuite la ligne CPU et vérifiez que les cases Show jump path et Show grayed path if jump is not taken soient cochées (cela nous aidera visuellement lors du débogage)
  • Sélectionnez ensuite la ligne Directories puis sous Location of API help file, cliquez sur le bouton à droite et sélectionnez votre fichier WIN32.HLP (cela permettra d'avoir une aide directe sur les APIs dans Olly en faisant un clic droit sur celle-ci et en sélectionnant Help on API function (ou Ctrl+F1), sous Directory for .udd files sélectionnez le dossier UDD que vous avez créé (ainsi tous vos fichiers .udd seront regroupés au même endroit) et enfin, sous Plugins Directory, sélectionnez le dossier Plugins.

Cliquez sur OK pour enregistrer vos modifications et c'est fini !

On va faire une pause avec la théorie et s'amuser un peu sur notre premier crackme (un crackme étant un programme spécialement conçu pour être cracké).

Kirjo / @2011 / http://xtx.a.nf/


Pré-analyse

------------------------------- Chapitre II - Quelques principes -------------------------------

Pré-analyse

Cible :

XTX-RegME

Outils nécessaires :

OllyDbg (by Oleh Yuschuk)
Peid 0.95 (by snaker)
RDG Packer Detector v0.7.0 (by RDGMax)
Protection ID v0.6.4.0 (by CDKiLLER & TippeX)
Protection ID v0.6.5.5 compatible Win 7 et 8 (by CDKiLLER & TippeX)
ResHacker v3.6.0.92 (by Angus Johnson)

(Tous les programmes et outils de ce tutoriel sont stockés dans le répertoire Pack).

1 - Introduction

Ouvrons notre cible pour voir comment elle se présente :

Tout d'abord une fenêtre d'accueil nous prévient que l'on est en présence d'une version de démonstration. C'est ce que l'on appelle un nagsceen, de l'anglais nag : houspiller, embêter + screen : écran. Ces fenêtres apparaissent souvent en début et/ou en fin de programme, et parfois durant l'utilisation de certaines fonctions afin de ralentir l'utilisateur et le pousser à acheter une licence pour les faire disparaître.

Puis, après un peu d'attente :

On a donc 3 versions plus ou moins limitées et on voit dans la barre de titre le nombre de jours restants pour notre version. On jette un coup d'œil au menu Fichier :

On voit qu'effectivement les commandes "Enregistrer" et "Enregistrer sous ..." ne sont pas activées. On regarde le menu Aide, on clique sur "Licence" et une boite de dialogue s'affiche :

Boite de dialogue serial

Nous avons une paire classique nom/serial à saisir. Nos objectifs seront donc :

  • supprimer le nag screen (patch)
  • supprimer la limitation de temps (patch)
  • activer les commandes grisées (patch)
  • passer le programme en version "familiale" ou "pro" (patch)
  • trouver un serial valide pour notre nom (fishing et keygening)

2 - Détection de protection

Nous allons prendre de bonnes habitudes. Dorénavant, avant de commencer à reverser une cible, nous allons systématiquement utiliser des scanners de protections. Ces petits utilitaires nous permettent de savoir sous quel langage a été conçu un soft, quel compilateur a été utilisé, quelle(s) protection(s) sont présente(s) ...

Je vais vous en présenter trois, les plus courants. Après, chacun aura son préféré qu'il utilisera plus régulièrement, mais il est toujours bon d'en utiliser un deuxième afin de valider les résultats du premier.

  • PEID

Il s'agit du plus connu. Lancer Peid.exe, cliquez sur [...] (1) pour sélectionner la cible à analyser, le résultat s'affiche (2) :

Aucune protection trouvée, programme codé en assembleur (Nasm et Tasm sont des compilateurs pour le langage asm).

  • RDG Packer Detector

C'est celui que personnellement j'utilise le plus souvent. Double-clic sur RDG Packer Detector v0.7.0.exe. A la première utilisation, aller sur MENU (1), puis config et dans Lenguaje, sélectionnez French.ini et fermez la fenêtre de configuration. Sélectionnez M-B (2) pour un scan plus approfondi, clic sur Ouvrir (3) pour sélectionner la cible, clic sur Detecter (4) et le résultat s'affiche :

RDG Packer Detector

On obtient le même résultat : pas de protection détectée, programme codé en asm.

  • Protection ID
  • Protection ID v0.6.5.5 compatible Win 7 et 8

Peut-être un peu plus complet en détection de protection que les deux premiers,il n'est pas compatible avec win 7 ou 8. Une nouvelle version semble être en préparation (la v6.5.5) mais la date de sortie n'est pas encore connue au moment où j'écris ces lignes. Pour pouvoir l'utiliser, il faut le passer en mode compatibilité XP SP3.

Lancez protection_id.exe, cliquez sur le bouton Log Window (1) pour avoir l'affichage des résultats, clic sur Scan (2) puis Single File, sélectionnez la cible, Ouvrir et le résultat s'affiche (3) :

Et toujours aucune protection détectée ...

Voilà pour une présentation simplifiée de ces trois outils, à vous de découvrir les différentes possibilités de chacun d'eux et de faire votre choix. Sachez que ces utilitaires se servent de fichier de signatures, pensez donc à faire des mises à jour de temps en temps afin qu'ils restent efficace.

Maintenant que nous savons que notre cible n'est pas protégée, attaquons le reverse ...

Kirjo / @2013 / http://xtx.a.nf/


XTX - Menus

------------------------------- Chapitre II - Quelques principes -------------------------------

Menus

Bon, nous n'avons plus de nagscreen, plus de problème de temps mais il nous manque encore des fonctions :

Avant de prendre Olly, nous allons essayer un petit utilitaire qui peut s'avérer très pratique. Il s'agit de ResHacker, un éditeur de ressources, qui permet non seulement de voir mais aussi de modifier les fenêtres, menus, icônes, images ... contenus dans un programme. Ouvrez le et chargez XTX-RegMe_NoEval.exe :

Sur le côté gauche (1), vous pouvez voir l'arborescence des ressources. En ouvrant les dossiers et en cliquant sur les icônes, vous avez sur le côté droit (2) soit une image (pour un bmp par exemple) soit un descriptif (pour un menu par exemple) ainsi qu'un aperçu fonctionnel (3). Ici, nous voyons les commandes des deux listes déroulantes "Fichier" (Enregistrer, Enregistrer sous ... et Fermer) et "Aide" (Licence, A propos de ...). A côté de chacune d'elles, le chiffre représente son identifiant (ID). Notez ceux qui nous intéressent (10001 & 10002) cela pourra nous servir plus tard. Comme vous pouvez le voir dans l'aperçu (3), les commandes Enregistrer et Enregistrer sous ... sont actives. Si elles avaient été créées non actives, nous aurions à coté de chacune d'elles la mention GRAYED (grisée) ou DISABLED (désactivée). Grâce à ResHacker, nous aurions pu tout simplement effacer ces mentions et enregistrer les modifications. Vu que ce n'est pas le cas, nous allons reprendre Olly.

Chargez XTX-RegMe_NoEval.exe. Vérifiez qu'il ne reste aucun BP ( ALT+B ou ALT+B). CTRL+N (ou clic droit Search for, Names) et regardons de nouveau les APIs présentes. L'une d'elles devrait retenir votre attention : EnableMenuItem (puisqu'elle parle de menu). Effectivement, cette API s'occupe uniquement d'activer ou désactiver des éléments de menusSélectionnez-la, CTRL+R (ou Find references). Double cliquez sur la première référence et regardons où cela nous mène :

Déjà, vous pouvez vous apercevoir que les 3 références trouvées pour cette API sont toutes regroupées ici. Jetons un œil aux arguments ( 1) poussés sur la pile avant l'appel. Nous avons l'Item. Ce chiffre (10001) ne vous rappelle rien ? C'est l'ID de la commande Enregistrez que nous a montré ResHacker (si vous regardez en dessous, vous verrez que le CALL suivant concerne Enregistrez sous ... (10002)). Ensuite le Flag. C'est lui qui va déterminer comment sera l'élément de menu. Il existe 4 possibilités :

  • 0 = MF_ENABLED (activé)
  • 1 = MF_GRAYED (grisé)
  • 2 = MF_DISABLED (désactivé)
  • 3 = MF_GRAYED:MF_DISABLED (grisé et désactivé)

Pour MF_GRAYED, cela correspond à PUSH 1 (2), il nous suffit donc de le modifier en PUSH 0. Ne vous précipitez pas sur la boite de dialogue Assemble, nous n'allons pas l'utiliser cette fois ci. En effet, il s'agit là d'un PUSH BYTE (taille de la valeur à pousser) dont l'opcode est 6A01 et avec Assemble (sous Olly v2), le PUSH va être traduit avec l'opcode 68 00000000 (opcode du PUSH DWORD) ce qui va provoquer un conflit de taille. Nous allons utiliser le binary edit qui nous permet de modifier le code hexadécimal. Clic droit sur la ligne du PUSH, Edit, Binary edit... (ou CTRL+E) :

et voici la fenêtre que nous obtenons :

Code hexa edit

Modifiez le 1 en zéro (vérifiez que Keep size soit coché) et cliquez sur OK. Le PUSH a bien été modifié mais l'argument n'apparaît pas. Faîtes CTRL+A pour forcer Olly à effectuer une nouvelle analyse et vous aurez :

Modifiez de la même manière les deux autres appels à EnableMenuItem, F9 (ou F9). Vérifiez le menu "Fichier" :

Cliquez sur l'une des deux commandes pour vérifier :

Nos menus sont bien actifs, le but est atteint. Mais n'y avait-il pas plus simple ? Fermez la boite de dialogue (pas le programme), faites CTRL+F2 ou CTRL+F2) pour annuler nos modifications, et revenez à nos appels à EnableMenuItem. Examinons un peu le code de cette procédure (délimitée par le crochet dans la marge (1)).

Les deux premières lignes (2) ne nous intéressent pas. Il s'agit du début classique d'une procédure (ces deux instructions servent à sauvegarder la pile et à faire une pile "vide" au-dessus). Nous avons ensuite une valeur transférer dans EAX (3) puis une comparaison entre AL et 0 (4) (souvenez-vous, AL est la partie basse de AX, lui-même étant le registre 16 bits de EAX), suivi d'un saut. Si ce JNE n'est pas pris, nous passons par l'API qui va grisée nos deux éléments de menus puis nous arrivons sur un LEAVE qui restaure la pile et un RET (5) qui nous sort de la procédure. Si le saut est effectué, nous arrivons sur une nouvelle paire CMP AL,1 / JNE (6). Cette fois-ci, si AL est égal à 1, une seule entrée du menu sera grisée (10002 = Enregistrez sous ...)(7) avant de sortir via le RET. Suivons le saut et nouvelle paire CMP AL,2 / JNE (8), suivi directement du LEAVE et du RET (9). Il ne fait donc rien sur nos entrées de menu :

Nous avons vu dans ResHacker que celles-ci étaient actives à la création, il suffit donc d'avoir AL = 2 pour qu'elles restent activées. Et ça tombe bien, au début de la procédure, nous avons justement une ligne qui donne sa valeur à EAX (3) :

0040258D MOV EAX,DWORD PTR DS:[XTX-RegMe_NoEval.404870]

nous allons la modifier en :

0040258D MOV EAX, 2

Clic droit sur la ligne, Assemble (ou touche Espace), remplacez DWORD PTR DS:[XTX-RegMe_NoEval.404870] par 2, vérifiez que Keep size et Fill with NOPs soient cochés, OK, F9 (ou F9) et vous pouvez vérifier que vos entrées de menu sont actives en ayant modifié qu'une seule ligne au lieu de trois précédemment.

Sélectionnez la ligne modifiée et enregistrez la nouvelle version sous XTX-RegMe_MenuOn.exe.

Et si maintenant nous nous occupions du titre.

Kirjo / @2013 / http://xtx.a.nf/


XTX - NagScreen

------------------------------- Chapitre II - Quelques principes -------------------------------

Le nagscreen

La première chose dont nous ne voulons plus, c'est cet écran au démarrage, le nagscreen.

Lorsque Windows affiche une fenêtre à l'écran, c'est le résultat d'une suite de commandes. Le programme va :

  • créer une classe pour la fenêtre (déterminer son icone, sa couleur de fond, ses menus ...) en utilisant l'API Register Class
  • créer une procédure (dans laquelle on pourra afficher une image, savoir quelle action effectuer selon où l'on clique ...
  • créer la fenêtre en lui attachant sa classe et sa procédure, pour cela il utilise l'API CreateWindow
  • montrer celle-ci à l'écran avec l'API ShowWindow
  • surveiller si une action est effectuée dans la fenêtre avec l'API GetMessage
  • renvoyer cette commande à la procédure avec l'API Dispatch Message.

Ce qui nous intéresse, c'est à quel moment notre nag apparait. On va donc utiliser l'API ShowWindow pour faire breaker Olly à ce moment-là.

On ouvre Olly, on charge notre cible (XTX-RegMe.exe) et on va lister les apis utilisées par notre programme. Pour faire cela, soit vous faîtes un clic droit dans le code puis Search for et Names, soit en étant dans le code vous utilisez la combinaison CTRL+N :

Ce qui nous donne cette fenêtre (pour trier les noms par ordre alphabétique, cliquez sur l'en-tête gris Name en haut de la colonne) :

On retrouve dans cette liste notre api ShowWindow. Clic droit après l'avoir sélectionnée, Find references et on obtient toutes les occurrences qui font appel à celle-ci :

On pose un BP avec F2 sur chaque call (ici on peut le faire manuellement avec F2 car les références ne sont pas nombreuses, sinon : clic droit, Breakpoint on all commands), on lance la cible avec F9 (ou F9). Olly breake et nous voyons clairement le processus de création d'une fenêtre :

Si vous regardez dans la partie création de la fenêtre, vous pouvez voir le titre (ce qui apparait dans la barre de titre en haut d'une fenêtre et dans la barre des taches) : XTX-RegMe, ce que nous avons lorsque le nagscreen s'affiche ainsi qu'un style : WS_POPUP . Ce style permet d'avoir une fenêtre sans bordure et sans barre de titre, ce qui correspond parfaitement. Tout nous indique que nous sommes au bon endroit.

Sur le côté, le crochet nous indique le départ et la fin de la procédure. Sélectionnez la ligne 40134Dh et saisissez un label (avec :) (1) et un commentaire (avec ;) (2) identiques : Nagscreen. (faire précéder les commentaires par => afin de les différencier de ceux d'Olly). Nous voulons maintenant savoir où se trouve le CALL qui nous a amené ici. Dans la partie information d'Olly, nous en avons un seul de noté (3) :

Clic droit sur la ligne d'information, Goto call 401C95 et on arrive ici :

Vu que c'est le seul endroit qui appelle la routine de création du nag, nous allons simplement remplacer le CALL par des NOP, comme nous l'avons fait au cours des exercices précédents. Pour ce faire, nous avions utilisé la boite de dialogue Assemble. Celle-ci est bien pratique si l'on veut modifier une instruction mais pour simplement "nopper" il y a plus rapide. Sélectionner la ligne du CALL, clic droit, Edit, Fill with NOPs :

et vous obtenez ceci :

Nous allons enregistrer nos modifications. Clic droit dans la partie grise, Edit, Copy to executable :

Une boite de dialogue s'affiche (afin de ne plus l'avoir la prochaine fois et de gagner un peu de temps, vous pouvez si vous le voulez cocher la case "Don't display this message in the future") :

Une nouvelle fenêtre s'ouvre, clic droit dans la partie sélectionnée, Save file :

Une nouvelle boite de dialogue nous demande encore si on veut sauver nos modifications ... Cliquez sur [Oui] :

On peut enfin enregistrer notre fichier modifié. Renommez-le en XTX-RegMe_NoNag.exe. Une fois fait, fermez Olly, démarrez le RegMe modifiée et là, plus de nag qui nous retarde l'ouverture. Objectif atteint !

On peut s'attaquer à la suite.

Kirjo / @2013 / http://xtx.a.nf/


XTX - Time limit

------------------------------- Chapitre II - Quelques principes -------------------------------

La limitation de temps

Nous allons maintenant by-passer la limitation de temps (time limit). Nous savons que la période d'évaluation est de 30 jours, regardons ce qui se passe après ce délai en avançant la date de l'ordinateur (2 mois, soyons large) . Exécutez XTX-RegMe_NoNag.exe et vous avez droit à ce message :

Clic sur OK et c'est fini, nous n'avons plus rien qui s'ouvre. Pour contourner cette protection, nous allons voir différentes approches.

1 - La boite de message

Chargez XTX-RegMe_NoNag.exe dans Olly. Le fichier ayant été renommé lors de l'enregistrement de nos modifications, Olly considère que c'est un nouveau programme et effectue une nouvelle analyse. Nos commentaires et breakpoints précédents ont donc disparu. Puisque l'on a une fenêtre qui s'affiche à l'écran, on va reposer nos BP sur les référence à l'API ShowWindow (CTRL+N, clic sur ShowWindow , Find references, F2). On lance avec F9 (ou F9) et ... pas de break. C'est normal car ce que nous avons à l'écran, même si cela y ressemble, n'est pas à proprement parler une fenêtre. Il s'agit d'une boite de message (DialogBox). C'est une sorte de fenêtre simplifiée comportant simplement un message, un ou plusieurs boutons et, éventuellement, une icône. Sa création est donc simplifiée et ne se sert pas de l'API ShowWindow mais uniquement de l'API MessageBox.

Redémarrez la session de débugging avec CTRL+F2 (ou CTRL+F2), CTRL+N pour réafficher nos APIs et effectivement on trouve :

Sélectionnez la ligne, CTRL+R (ou clic droit, Find references) pour afficher les références dans le code :

Reference to MessageBoxA

On pose un BP (F2) sur chaque CALL, excepté le 3eme car on voit bien que le texte passé en argument ne correspond pas à notre message. F9 (ou F9) pour lancer le programme, Olly breake ici (1) :

Un peu en dessous de notre CALL, on voit un appel à l'API ExitProcess. C'est elle qui termine le programme lorsque l'on clique sur OK. Regardons maintenant au-dessus ce qu'il se passe. Dans la marge, se trouve une petite flèche (2) : elle indique qu'un saut nous amène à cette ligne et donc indirectement à notre MessageBox. Sélectionnez la ligne correspondante (3) afin que s'affiche dans la fenêtre d'information l'adresse du saut d'origine (4). Clic droit, Go to JA from 00401BAA nous donne ceci :

JA message box

Juste au-dessus du JA (Jump if Above : saut si supérieur), nous avons une comparaison entre la valeur du registre eax et 1Eh (1) soit l'équivalent de 30 en hexadécimal. Ne serais-ce pas le nombre de jours d'évaluation ? Il suffirait donc que le saut ne soit pas effectué et nous pourrions peut être continuer d'utiliser le programme après ce délai. On va tester cela. Posez un BP (F2) sur la ligne du JA, redémarrez la cible avec CTRL+F2 (ou CTRL+F2), lancez avec F9 (ou F9), et on breake sur notre saut. Pour inverser un saut, il n'est pas nécessaire de modifier le code, on peut utiliser les flags (indicateurs ou drapeaux) :

Ces indicateurs ne peuvent avoir que deux valeurs : 0 ou 1. Ils nous indiquent le résultat d'une opération. Par exemple, dans le cas de notre cmp eax, 1Eh si eax est égal à 1Eh, ZF sera mis à 1 (on parle de ZF, pour Z Flag). Ce qui nous intéresse pour le moment c'est le JA. Pour lui, le saut sera effectué si ZF et CF sont égal à zéro. Il suffit donc de modifier l'un des deux. Sélectionnez par exemple la valeur de ZF, faites Entrée et il passe à 1 (inversement, si il avait été à 1, il serait passé à 0). Vous pouvez voir dans la fenêtre d'information sous le code que l'indication Jump is taken est devenue Jump is not taken. F9 (ou F9) deux fois (Olly breake sur notre précédent BP, ShowWindow) et bingo le programme redémarre. Vous pourriez penser qu'il suffit donc de nopper ce saut comme nous l'avons fait précédemment mais nous constatons que cela provoque un bug dans le titre de la fenêtre :

Bug jours restants

Nous verrons dans la prochaine approche comment le modifier.

2 - La recherche de constante

Repartons sur une session de débuging propre. Faîtes tout d'abord un restart ( CTRL+F2 ou CTRL+F2) puis cliquez sur ALT+B (ou ALT+B) et effacez tous les BP.

Nous savons que nous avons 30 jours d'évaluation et que 30 converti en hexadécimal donne 1Eh. Nous pouvons donc déduire que, quelque part dans le programme, une comparaison va être faîte entre le nombre de jours restants et cette valeur. Si cette valeur a été saisie en dur dans le code, par exemple :

mov ecx, 1Eh

on parle d'une valeur constante (et nous verrons cette ligne telle quelle dans Olly), en opposition à celle mémorisée dans une variable, par exemple :

.data
;initialisation de la variable avec la valeur 1Eh
MaVariable db 1Eh

Code :
...
mov ecx, MaVariable
...

et dont la ligne apparaîtra dans Olly sous la forme :

MOV ECX,DWORD PTR DS:[XTX-RegMe_nonag.4046A0]

[XTX-RegMe_nonag.4046A0] étant l'adresse de l'emplacement mémoire où est stockée la valeur de MaVariable.

Regardons si nous pouvons retrouver des constantes égales à 1Eh. Placez-vous sur la première ligne de code dans Olly, faites un clic droit puis Search for, Constant ...

Dans la zone de saisie, tapez 1E (les valeurs de constantes sont toujours en hexadécimal), sélectionnez le type de recherche (Entire block = tout le code, Forward = vers le bas, Backward=vers le haut) puis OK et nous nous retrouvons sur la première occurrence :

Ça ne vous rappelle rien ? Nous nous retrouvons juste au-dessus du JA de tout à l'heure. Ici, nous savons que c'est le bon endroit car nous l'avons étudié précédemment. Faisons comme si nous ne l'avions pas fait. Posez un BP sur la ligne, puis faites CTRL+ L (ou clic droit, Search for, Search again). Un deuxième résultat est trouvé. On pose un BP sur la nouvelle ligne, et ainsi de suite tant que l'on trouve la valeur 1Eh (ici nous n'avons que ces deux résultats). On redémarre ( CTRL+F2 ou CTRL+F2), on lance avec F9 ( ou F9) et on breake sur notre CMP. Il ne vous resterait plus qu'à tester l'inversion du saut, mais nous l'avons déjà fait. Comme promis, voyons comment modifier notre code pour avoir un résultat un peu plus élégant.

Pour que le saut ne soit pas pris, il suffit qu'eax soit inférieur à 1Eh. Regardez la ligne au-dessus du CMP. Une valeur est soustraite à eax pour obtenir le résultat qui va être comparé. Cette opération n'a pour nous aucune importance puisque nous voulons donner à eax une valeur fixe inférieure à 1Eh. Autant la remplacer par une instruction fixant cette valeur. D'autre part, cette ligne de code est suffisamment longue pour nous laisser de la marge dans la modification envisagée. Effectivement, lorsque l'on modifie une ligne de code, la nouvelle doit toujours être de longueur inférieure (en complétant avec des NOP) ou égale à l'ancienne (dans sa représentation hexadécimale) mais jamais plus longue sous peine de décaler (ou même fausser) la suite du code. Regardez ce petit exemple :

Nous allons remplacer CMP EAX, 1E (en hexa : 83F81Eh) par MOV DWORD PTR DS:[404880], EAX (en hexa : A380484000h )

Voici le code de départ :

1er cas : on décoche Keep size puisque l'instruction est plus longue :

La ligne du CMP a bien été modifiée mais vous pouvez voir que nous avons débordé sur la ligne du JA et que cette ligne a été complétée par des NOP pour arriver à la ligne MOV ECX, 1E. Nous avons donc supprimé une ligne qui pourrait être utile à la bonne continuation du programme.

2eme cas : on décoche aussi Fill rest with NOPs pour supprimer moins de choses :

Replace cmp without NOP

Ici aussi on déborde sur la ligne du JA mais comme nous n'avons pas compléter avec des NOP, le code devient incompréhensible et c'est le crash assuré ...

Voilà pourquoi il faut toujours s'assurer que nous avons la place nécessaire lorsque l'on veut modifier des instructions.

Revenons à notre cas. A la place de la soustraction en 401BA1h, nous allons transférer dans eax la valeur 1 (par exemple, ou n'importe quelle autre valeur tant quelle est inférieure à 1Eh) à l'aide d'un simple MOV. Sélectionnez la ligne, appuyez sur Espace (ou clic droit, Assemble ) puis saisissez notre commande :

Vérifiez que les cases Keep size et Fill rest with NOPs soient cochées, Assemble et nous obtenons :

Nous n'avons pas débordé sur le CMP EAX, 1E qui suit, c'est parfait. Sélectionnez les lignes modifiées (pour sélectionner plusieurs lignes, soit clic sur la première, maintenez la touche MAJ enfoncée et clic sur la dernière, soit clic sur la première, maintenez la touche CTRL enfoncée et clic sur chaque ligne à sélectionnée), clic droit, Copy to executable puis suivez la procédure pour enregistrer votre exe modifié. Renommez-le par exemple en XTX-RegMe_NoEval.exe.

On va tester notre modification. Ouvrez le nouvel exe obtenu dans Olly. Faites un clic droit puis Search for , Constant ..., recherchez la valeur 1Eh ce qui va vous amener directement sur le CMP. Posez un BP (F2) sur la ligne et lancez avec F9 (ou F9). Olly breake comme prévu :

Jump not taken

On note qu'eax est bien à 1 comme nous le voulions (1). F8 (ou F8) pour passer sur la ligne du saut. Celui-ci ne sera pas effectué (2). F9 (ou F9) et nous obtenons :

Eval 29 jours

Nous avons maintenant éternellement 29 jours d'évaluation et la barre de titre est quand même mieux comme ça qu'avec les hiéroglyphes obtenus en noppant simplement le JA.

3 - L'API GetSystemTime

Reprenez dans Olly le XTX-RegMe_nonag.exe et supprimez tous les BP ( ALT+B ou ALT+B).

Pour savoir depuis combien de temps il est installé au moment où on l'ouvre, le programme a forcément besoin de connaitre la date du jour. Faites un CTRL+N (ou clic droit, Search for, Names) et regarder la liste des APIs. Nous pouvons voir quelque chose en rapport avec le temps : GetSystemTime. En effet, cette API récupère le temps au format universel UTC (cela aurait pu être GetLocalTime pour date et temps en local). Voyons si cela peut nous servir.

Sélectionnez cette API, faites CTRL+R (ou clic droit, Find references). Dans la fenêtre qui s'ouvre, nous avons un seul CALL. Cette fois-ci, nous n'allons pas poser directement un BP dessus mais plutôt aller voir où il est utilisé. Faites un double-clic sur la ligne et automatiquement vous vous retrouvez au bon endroit dans la fenêtre code ( ALT+C ou ALT+C si elle n'est plus visible) :

Vous pouvez voir l'appel à la fonction avec en arguments les infos qui vont être récupérées (1). Le crochet dans la marge (2) et le RETN à la fin nous indique qu'il s'agit d'une procédure appelée par des CALL. En nous positionnant sur la 1ere ligne de cette procédure, nous pouvons voir ceux-ci (3).

Remontons donc ces appels. Clic sur la ligne Local calls from ... (3) pour la sélectionner, clic droit puis Go to call from 401B9C et nous arrivons dans la zone de test que nous commençons à bien connaître. Mais faisons comme si ... On pose un BP (F2) sur la ligne et pour revenir à notre fonction, il suffit d'appuyer sur Entrée (l'appui sur Entrée lorsqu'on est sur un CALL ou un saut permet d'aller à l'adresse visée sans exécuter le code). Re clic droit puis Go to call from 4020F4 et on pose ici aussi un BP. F9 (ou F9) et bien évidemment Olly va breaker sur le premier CALL juste avant le CMP EAX, 1E.

On en a fini avec le time limit, vous pouvez remettre votre pc à la bonne date.

Tout ce que nous venons de faire pour une seule protection vous montre que plusieurs méthodes sont souvent possible pour arriver au même résultat. Certaines marchent sur un programme et pas sur un autre et vice-versa. C'est surtout, en plus d'un peu de technique, l'observation et la réflexion qui vous permettront d'atteindre vos objectifs.

Continuons à nous entrainer sur notre cible ...

Kirjo / @2013 / http://xtx.a.nf/


dotNET - MeTodd

-------------------------------------- Chapitre IV - dotNET ---------------------------------------

Analyse de Todd "traditionnelle" avec les outils .Net - 2/3

Nous avons donc identifié la partie où sont vérifiées les informations saisies :

Code :

Mes commentaires vous permettent de voir clairement ce qui se passe.
Pourquoi j'en suis arrivé à ces conclusions ? Voyons dans le détail chaque partie...

Mais avant tout, chacun aura vu que "RegistrationCode" doit être égal à " str" , donc la génération du code se fait en "m000144" sur la base des informations saisies (RegisteredUser).

Code :

Ici rien de bien utile, il faut encore descendre dans le code et voir en "m000057".

Code :

Tout est là !
Vous aurez remarqué que j'alterne entre le C# et le VB parce qu'il est parfois plus simple de comprendre l'un ou l'autre selon la situation. Et il y en faut pour tous les goûts !
La première partie passe en revue chaque caractère du Nom pour n'en garder que ceux inclus dans 2 plages qui sont très probablement de "a" à "z" ou "A" à "Z"... il est peu probable qu'une des plages soit de "0" à "9" pour un nom !?
Donc tout autre caractère n'appartenant pas à l'alphabet est ignoré.
Ensuite l'adresse email vient s'ajouter au résultat de cette épuration.
Et pour finir, on récupère les 16 premiers caractères du hash SHA-1 de notre précédent résultat (NomEpuré+Email).

Todd / ©2011 / http://xtx.a.nf/


dotNET - MeTodd

-------------------------------------- Chapitre IV - dotNET ---------------------------------------

Analyse de Todd "traditionnelle" avec les outils .Net - 3/3

Voilà, je vous ai passé les détails, mais chacune de mes affirmations peut être vérifiée en poussant les investigations. Par exemple, je me suis assuré que la fonction "GetSHA1" qui ne fait pas partie du framework (propre au développeur) utilise bien System.Security.Cryptography.SHA1CryptoServiceProvider et aucune autre bidouille.

Les plus curieux pourront poursuivre l'analyse, pour comprendre pourquoi je parle, plus haut, d'une blacklist :

Code :

En creusant un peu, les connaisseurs verront que les informations saisies sont envoyées à un serveur pour refus ou non. Par défaut le programme accepte l'enregistrement si le serveur est inaccessible ! Dans un premier temps j'ai cru qu'il avait trouvé LA parade à la fraude ... et non, en fait il se contente de refuser l'enregistrement sur la base d'une liste noire. Elle contient certainement les licences expirées et celles utilisées par de nombreux utilisateurs.

4) Conclusion :

A part le renommage de tous les identifiants (Classes, Variables, etc.) en nom long et complexe, ainsi que le chiffrage des SDRs, on ne peut pas dire que la protection utilisée soit suffisante ! D'ailleurs on a pu voir encore quelques mots clés !
Il faut savoir que les obfuscateurs permettent d'exclure certaines portions de code afin de ne pas diminuer les performances de l'application, mais quand les réglages ne sont pas correctement définis (comme ici ), beaucoup d'indices restent visibles (Name, Email, RegistrationCode, etc.) !
Côté "brouillage" (flow control), il n'y a rien ! Donc finalement je ne suis pas sûr que ce soit le travail de Dotfuscator qui habituellement abuse des instructions "Switch" et "Goto".
Pour finir, je dirais que se limiter à inclure le contrôle des licences dans une liste noire alors qu'une licence non enregistrée sur le serveur (donc non achetée) n'est même pas vérifiée , c'est pas terrible ! D'autant que ce système ne fonctionne que si internet est ouvert. L'auteur aurait pu intégrer le refus d'une licence non générée par lui, rendant inutile la création d'un keygen ! On peut également regretter l'absence de contrôle ultérieur de cette fausse licence, pour verrouiller l'application quand internet sera de nouveau opérationnel !
Dans l'ensemble, on appréciera le léger effort et la prise de conscience, mais "Peut mieux faire !"

5) Annexe :

Pourquoi ne pas jeter un oeil sur comment forcer l'application à accepter n'importe quelles informations saisies, puisqu'il n'y a aucune vérification par la suite !?
Tout le monde aura compris qu'il faut que "m000145()" retourne "true" ! Et comme précisément, c'est la variable "flag" qui est retournée et qu'elle est initialisée à " false", on va changer cet état !
La difficulté réside en le fait que l'exécutable désobfusqué est bogué et ne correspond plus au fichier d'origine ! Je vais donc vous montrer par cette petite vidéo comment s'y retrouver. Je passerai volontairement tous les détails de l'analyse précédente et utiliserai un outil perso, qui est ni plus ni moins qu'un éditeur héxa, mais qui a l'avantage de simplifier ma démonstration.

Lien alternatif pour la vidéo: http://www.megaupload.com/?d=BJP8GVR8

Todd / ©2011 / http://xtx.a.nf/


dotNET - MeTodd

-------------------------------------- Chapitre IV - dotNET ---------------------------------------

Analyse de Todd "traditionnelle" avec les outils .Net - 1/3

1) Introduction :

Nous allons étudier la protection d'une application .NET que je qualifierais de niveau 2,5 sur une échelle de 5. Ce cours ne s'adresse donc pas aux débutants, car l'impasse a été faite sur la manipulation des outils et certaines notions élémentaires ne seront pas abordées. On verra que beaucoup d'erreurs ont été commises et qu'il est relativement simple de la cracker et même d'en générer un keygen.

2) Observations :

PEiD en perd son latin, alors que RDG Packer Detector y voit du VB.Net protégé, ce qu'on pourra constater par nos propres yeux sous Reflector.
Cette protection n'étant pas signée, on demande alors à quelques désobfuscateurs de nous donner leur avis... seul DeDotfuscator semble s'y retrouver et fournir un résultat convenable. Donc notre cible est... euh était probablement protégée par Dotfuscator !
Notre programme est maintenant désobfusqué et légèrement plus clair car il est plus facile d'assimiler des noms courts/ alphanumériques comme "m000561" plutôt que "#=qxD4no7OLBmabYHhyUKhSdcEBGMLi5gMfENHXkHaj GFU=".
Concernant les constantes et chaînes de caractères (SDRs), elles sont chiffrées ... il faudra donc s'en passer et admettre qu'elles se cachent sous la forme "c000052.m000149(-134001454)" où "-134001454" est l'identifiant (constante) de la chaine masquée.
Pour la suite, seul Reflector sera utile étant donné que l'objectif est d'obtenir un keygen.

3) Analyse :

Comme en programmation, le cracking peut s'aborder d'un millier de façons, mais on verra celle que j'ai choisi après coup comme étant la plus directe pour notre cible.
Allez savoir pourquoi... l'obfuscation n'est jamais complète et il y a toujours des traces de mots clés. Je cherche "register" et tombe sur une structure nommée "RegisteredUser" qui comporte 3 propriétés (Name, EMail, RegistrationCode) ! Même si je n'ai pas trouvé le terme "Pour me cracker c'est ici !", ça nous fait déjà un bon début. On demande à Reflector d'analyser cette structure (Ctrl+R) et on vérifie qui s'en sert (Used By) :

Code :

Là on pourrait tout passer en revue, mais parmi nos résultats, un attire particulièrement mon attention de par sa signature "(Object, EventArgs)" ! Il s'agit d'une procédure appelée lors d'un click de souris.
Que cache ce "c000027.m000561(Object, EventArgs)" ?

Code :

Bon là aussi c'est évident , le programme vérifie que 3 zones de texte ne soient pas vides... suite au click sur le bouton "Vérifiez l'enregistrement" !? Pour ceux qui auraient encore des doutes, "c00005e.m00026c()" et " c00005e.m00026d()" font appel à une msgbox.
Maintenant la condition "if (this.f000318.m000145(this.m000560(), true))", doit-elle être valide ou non ? Quelle est la bonne msgbox ? On n'a pas de SDRs !
Allons Watson, "this.f000318.m000148" parle de "GNconfig.Save();", alors " this.f000318.m000145(this.m000560(), true)" doit retourner "true".

Todd / ©2011 / http://xtx.a.nf/


dotNET - Sous Olly

-------------------------------------- Chapitre IV - dotNET ---------------------------------------

Analyse sous Ollydbg - 1/4

Passons l'introduction, étant donné que Todd l'a déjà faite, et attaquons directement. :)

Pour ce tuto, j'avais utilisé la version 1.10 d'Olly. Logiquement, vous devez être en mesure de le suivre avec Olly v2, l'analyse ne pourra en être qu'améliorée. La V2 interprête le code MSIL et d'une manière générale, elle présente plus de détails.
Vous pouvez essayer par vous-mêmes si vous ne me croyez pas, mais lorsque l'on charge un .NET dans Olly et que l'on exécute, le programme se lance et rien n'apparaît sous Olly... Assurément une des protections du framework !
Donc, nous allons lancer le programme d'abord, ouvrir Olly et s'attacher à son processus ensuite.

Pour ce faire, on utilise le menu File / Attach et on choisit le processus qui nous intéresse. Voir fig.1 ci dessous.

Fig.1

Vous arrivez sur un Breakpoint dans le Module Ntdll (Fig.2)

Fig.2

Relancez en cliquant simplement sur F9 ou sur Run.
Nous sommes donc maintenant attachés au processus, qui est en cours d’exécution.

Revenons sur notre fenêtre d’enregistrement, et remplissons les 3 champs requis (Notez que le code ne peut pas excéder 010h -soit 16 en décimal- caractères), et on valide.

Surveillez de près votre chère et tendre si vous n’obtenez pas ceci :

Fig.3

Revenons à nos moutons. Le but maintenant va être de « remonter » dans le thread principal de l’application, pour tenter de trouver un endroit proche de la vérification avant que la vilaine MessageBox ne soit appelée. Pour cela, on met en Pause [F12] le programme et on lance des « Execute till return » (je ne vous fais pas l’affront de traduire ) [CTRL+F9], jusqu’à ce que le programme nous rende la main, et nous permette de valider le message d’erreur.

TIP : vous pouvez rester appuyer sur CTRL+F9 ;) ça va plus vite.

Une fois que vous le pouvez, validez en cliquant sur OK, Olly devrait breaker sur le prochain RETN et vous devriez tomber ici :

Fig.4

Papa Bango / @2011 / http://xtx.a.nf/


dotNET - Sous Olly

-------------------------------------- Chapitre IV - dotNET ---------------------------------------

Analyse sous Ollydbg - 2/4

Un petit Step over [F8] pour effectuer le ret, puis on va faire une suite d’exécutions jusqu’au RETN suivi d’un F8 [CTRL+F9 puis F8] jusqu’à ce que l’on soit remonté dans le « CPU - Main Thread ». J’en fais 4 de suite pour arriver là mais l’important c’est de voir le « Main Thread » dans la barre de titre d’Olly.

Vous devriez voir ça :

Fig.5

Rien d’intéressant à priori à cet endroit, on va donc continuer à remonter gentiment en utilisant le même procédé.
On effectue quelques CTRL+F9 puis F8 (2 ou 3, zut je ne m’souviens plus) et on arrive par ici :

Fig.6

Il y a de fortes chances pour que les adresses dans Olly ne soient pas les mêmes chez vous… alors pour que l’on se comprenne un peu mieux, je vous invite à passer en « Adresses relatives » à partir du MOV EBP,ESP (qui se trouve à l’adresse 00D1FC11 chez moi sur l’image ci-dessus), en double-cliquant sur l’adresse de la ligne correspondante. Comme ça, nous auront les mêmes adresses relatives, ce sera plus facile à suivre. :) (Fig.7)

Papa Bango / @2011 / http://xtx.a.nf/


dotNET - Sous Olly

-------------------------------------- Chapitre IV - dotNET ---------------------------------------

Analyse sous Ollydbg - 2/4

Fig.7

Fig.8

Nos soupçons se confirment et les 2 prochains récupèrent l’eMail et sa longueur, puis le Registration Code afin de contrôler si les champs ne sont pas vides. Continuons de tracer pour s’en assurer.On arrive là :

Fig.9

Maintenant que nous sommes prêts, on remarque tout de suite que nous serions passés au-dessus du CALL d’où l’on vient en $+B6, si le saut avait été pris en $+75…(Fig.7) On peut donc imaginer qu’il va être intéressant d’aller voir ce qu’il va se passer dans le CALL en $+6E. Posons-y donc un BP [F2] (Notez que si vous redémarrez la cible dans Olly, les BP ne seront pas conservés étant donné que toutes les adresses auront changées, il vous faudra reprendre depuis le début…)
Jetons un œil un peu plus haut encore, et nous remarquons encore une fois quelque chose d’intéressant … On y aperçoit 3 occurrences successives de ces 2 lignes :

CALL DWORD PTR DS:[EAX+164]
CMP DWORD PTR DS:[EAX+8],0

Tiens tiens, comme c’est étrange, nous avons justement rempli 3 champs pour l’enregistrement, et ici, on teste 3 fois si une valeur est non nulle… « O…D…I…L… je me demande ce qu'il voulait nous dire » (Ok, je sors !).
Posons un BP là aussi !

Bon nous avons assez d’éléments pour tester notre flair. Et pendant que je suis dans les répliques :
« -Et Chef ?
-Oui mon petit ?
-Comment on fait pour devenir Chef, Chef ?
-Le flair mon petit…le flair ! »
C’est promis, c’était le dernier, après j’arrête.

On lance la bête [F9]on clique sur OK quand la MessageBox apparaît et revérifie notre Serial, pour voir si on break bien où on l’espère.

Bam, Olly break en $+E sur le premier CALL DWORD PTR DS :[EAX+164], on fait un petit F8 pour voir ce qu’il en ressort. Premier signe positif, la valeur comparée à 0 est 0Ah (soit 10d, ce qui correspond à la longueur de mon Name : Papa Bango).
Dans la fenêtre des registres, faîtes un clic droit sur EAX (qui contient la valeur de retour de la fonction), puis « Follow in Dump » (Fig.8).

Continuons de tracer. Laissons tomber le premier CALL rencontré… en observant la fenêtre de dump, on comprend qu’il ne fait que récupérer une seconde fois les informations d’enregistrement, et entrons donc dans le CALL à l’adresse $6E (F7), puis passons à nouveau en adresse relative sur le PUSH EBP où l’on arrive.

Fig.10

Les premières instructions, vous l’aurez compris, vérifient une nouvelle fois, la valeur non nulle des trois champs…Quelle redondance le .NET ! Je ne sais pas si c’est lié au fait que ce soit programmé en .NET, mais on commence à mieux saisir l’intérêt d’utiliser un désassembleur adapté au langage…encore faut-il le comprendre :)
Rentrons maintenant dans le Call.

Vous voyez que cette analyse sous Olly, comme je le disais en prélude, est "intuitive" et peut difficilement être méthodologiquement expliquée.
Pour éviter de vous donner la marche étape par étape à coup de 12 F7, 3 F8, à droite puis à gauche...STOP! C'est là.... je vous donnerai les étapes grossières puis vous travaillerez tout seul :D.

A ce stade on tombe dans une routine:

Fig.11

Papa Bango / @2011 / http://xtx.a.nf/


dotNET - Sous Olly

-------------------------------------- Chapitre IV - dotNET ---------------------------------------

Analyse sous Ollydbg - 4/4

Rentrez dans le second call et tracez pas à pas tout en observant la zone de Dumps, jusqu'à la fin de cette sous-routine jusqu'ici. (Fig. 12)
En suivant le dump, vous vous apercevrez que tout au long de la routine, il ne fait que préparer la chaine à hasher :

- Nettoyage du Name (Retrait des espaces et autres caractères spéciaux, ainsi que des valeurs numériques)

=> Encore une fois, dans ce cas, avec Olly, il faut y aller de son intuition... il va être difficile (bien que possible, en rentrant dans chaque call, et avec un sacré blocnotes) de déterminer la plage de caractères autorisés exacte. Pour ma part, après avoir vu que d'un Papa Bango, il ne retenait que PapaBango, j'ai essayé avec un Name contenant d'autres caractères spéciaux que les espaces. Par exemple, si vous utilisez le name -=P4ap4a _ Bang0o=-, seul PapaBango sera retenu pour la concaténation. On peut en déduire que le Name ne doit contenir que des caractères compris en (a - z) et (A - Z). Mais si un développeur rusé avait décidé d'exclure également toutes les voyelles de la plage autorisée, avec Olly, il serait déjà plus difficle de le déterminer.

- Concaténation du Name avec le mail.

Si vous ne me croyez pas, et bien relisez le tuto de Todd ou tracez vous-même dans les méandres de CALLs, et vous pouvez me faire confiance... c'est fastidieux ;)!

Fig.12

Regarder la zone dump à l'issue du CALL à l'adresse 04A614AC (chez moi, hein!), et que voit-on...un beau Hash. Encore une fois, à l'instar de la méthode Toddienne, on ne voit pas tout de suite qu'il s'agit d'un HASH SHA1, on peut cependant le soupçonner par tâtonnements. Tout d'abord, on s'aperçoit que c'est un Hash d'un longueur de 40 Bytes...comme le SHA-1 :) :

Fig.13

Comme on a de l'intuition, on pourrait tout simplement tester de hasher notre chaine concaténée en SHA1 et la comparer, mais histoire d'avoir quelques certitudes, recherchons en mémoire les éventuels vecteurs de hashing. Par défaut, les 5 vecteurs d'initialisation du SHA1 sont :

  • 67452301
  • EFCDAB89
  • 98BADCFE
  • 10325476
  • C3D2E1F0

=> ALT+M pour ouvrir la fenêtre Memory sous Olly, puis en HEX, cherchez une des constantes (maintenant, je prends la dernière, vu que les 4 autres sont similaires aux quatres vecteurs du MD5...).

TIP: Attention, en HEX on cherchera : F0 E1 D2 C3 ;) :

Fig.14

Bingo! On reprend le tracing où on en était, et le CALL à l'adresse 04A614BC (toujours chez moi) récupère manifestement les 16 :) premiers caractères du Hash Obtenu.... et combien de caractères doit faire notre serial? je vous le donne en mille : 16d ! CQFD

Comme l'a précisé Todd, le programme se connecte pour comparer le Name et l'EMail entré, à une base de données de BlackList. C'est une condition non indispensable à la validation, donc vous devez couper le Net pour la vérification, le programme ne se reconnectera plus à la base. Mais tant que vous n'apparaissez pas comme blacklisté dans la base, la validation devrait se faire quand même :).

Papa Bango / @2011 / http://xtx.a.nf/


dotNET

-------------------------------------- Chapitre IV - dotNET ---------------------------------------

Conclusion

Je conclurai simplement car l'essentiel a été dit...

Ce qu'il faudra retenir c'est qu'on peut attaquer l'analyse d'un programme en .NET de différentes manières et qu'il est intéressant de comprendre le lien entre les deux approches présentées ici. Un programme en DotNet n'est pas un binaire traditionnel comme ceux réalisés en ASM. Disons qu'ils ont comme seul point commun leur structure, au même titre qu'un programme DOS, afin que Windows sache quoi en faire. Pour ce qui est de l'écriture même du programme, on est en face d'un langage intermédiaire (MSIL = MicroSoft Intermediate Language) incompréhensible par le processeur et c'est la raison pour laquelle OllyDbg v1.10 ne peut pas en faire une analyse statique, mais il doit s'attacher au processus déjà lancé.

L'interrogation de PaPa Bango va trouver réponse ici : Le framework .NET est une machine virtuelle, comme celle de WinDev ou d'autres, et c'est lui qui se charge de la conversion "à la volée" du code MSIL en assembleur afin que l'application soit "compatible Windows".
A partir de là, Olly peut déboguer l'application, mais vous aurez compris que toute modification est temporaire !

Toutefois OllyDbg v2 est capable au même titre que Reflector de nous présenter ce code IL... mais il est un peu moins pratique et le débogage se fera toujours sous la forme ASM et non MSIL !

Pour finir, je dirais qu'en terme de protection logicielle, le principal point faible du DotNet est l'extrême simplicité à le reverser... d'où l'intérêt d'utiliser des techniques d'obfuscations sur le code source. Cependant sur cette cible, on peut voir que cette technique n'est pas maitrisée par son auteur ou que ses connaissances en reverse engineering sont insuffisantes !

Todd / ©2011 / http://xtx.a.nf/


MD5

---------------------------------------- Chapitre VII - Hachage ----------------------------------------

Deux exemples de protection basée sur des hash MD5 - 1/2

Cette fois-ci, nous allons voir deux exemples de protections basées sur des hash MD5.

Outils nécessaires :

OllyDbg (by Oleh Yuschuk)
Keygener Assitant v1.7 (by Mr Paradox & RobenHoodArab, http://www.at4re.com/) pour les calculs, ou tout autre outil calculant des hashs MD5. J'aime bien Keygenner Assistant pour son aspect "Tout en un" et également car il intègre un Sniffer de hash et de crypto.
PEiD et son plugin kanal (by Jibz, Qwerton & snaker, http://www.peid.info/) ou tout autre analyseur d'exécutables !

- Les notions de base des précédents cours sur l’assembleur et l’utilisation d’Ollydbg sont considérées acquises.

RAPPEL

Tout le contenu de ce tutoriel est fourni à titre purement éducatif, et ludique. N'utilisez pas ce logiciel en dehors des autorisations fixées par la licence d'utilisation de celui-ci. Si vous ne disposez pas de licence valide pour ce logiciel, vous devez le désinstaller à l'échéance de sa période d'évaluation. Nous ne sommes en aucun cas responsable de l'usage que vous faites de ce programme et de ces tutoriels.

I - Premier cas : RegistryFast

Cible : RegistryFast v5.0
Editeur : RegistryFast.com

Je pense que l'on pourrait y attribuer un niveau de difficulté de 1 ou 1.5 sur une échelle de 5 (le serial correct n'étant pas comparé directement avec ce qui a été entré), l'exécutable n'est pas packé, ni obfusqué. De plus, aucun anti-debugger ni quoi que ce soit pour contrer l'analyse n'a été intégré...

Notre premier réflexe est bien sûr de scanner notre exécutable principal avec notre analyseur préféré. D'une manière générale, ProtectionID est celui que j'utilise le plus souvent pour la fiabilité de ses signatures et ses mises à jour encore régulières, mais, pour ce cas, je vais utiliser PEiD et le plugin Kanal, qui permet la détection de signatures cryptographiques :

Nous apprenons que notre cible est codée en Delphi, et surtout qu'elle ne semble pas être packée. Par ailleurs, Kanal nous indique l'utilisation d'un certain nombre de fonctions de crypto, que nous laisserons de côté pour le moment...

Ouvrons donc notre cible avec Olly, et tentons de nous enregistrer. Utilisez deux caractères numériques pour les 2 premières valeurs de votre serial... ou bien vous aurez droit à un beau: " "Votre lettre" is not a valid integrer value! " et vous n'obtiendrez pas le "bon" message d'erreur :

On va pouvoir commencer à travailler :). Mettez Olly en Pause et allons jeter un œil dans la pile des Calls, appelé "Call Stack" :

Le but est de trouver un point antérieur à l'arrivée de la MessageBox, le plus prêt possible de la routine.
On aurait également pu faire des exécutions jusqu’au RET, nous serions arrivés au même point, mais il faut savoir s'économiser quelques clics ;). Vous allez double-cliquer sur la ligne à l'adresse 0012FB6C, ce qui nous conduira en 004DCAA4. En toute logique, il faudrait également aller voir en 0048A630... mais il n'y a rien d'intéressant là-bas ;).
Donc une fois en 004DCAA4, faîtes un petit coup de scroll down, voir si on peut se faire une idée de ce qui se trame dans cette routine...et Ô bonheur :

004DCC81 |. B9 50CD4D00  MOV ECX,RegFast.004DCD50     ; ASCII "Registry Fast"
004DCC86 |. BA 60CD4D00  MOV EDX,RegFast.004DCD60     ; ASCII "Invalid serial number! Please try again..."
[...]
004DCCA9 |. B9 50CD4D00  MOV ECX,RegFast.004DCD50     ; ASCII "Registry Fast"
004DCCAE |. BA 60CD4D00  MOV EDX,RegFast.004DCD60     ; ASCII "Invalid serial number! Please try again..."

Nul doute, nous devons être au bon endroit ;).

Posons donc un BP en 004DCAA4, et on recommence. Je saisis mon fake serial, pour moi, ce sera : " 59ABCDEFGHIJKL1111222223344445555" (notez que même si vous saisissez en minuscules, les caractères seront automatiquement mis en majuscules). N’oubliez pas de commencer par deux chiffres, puis on teste notre serial.

Comme prévu, nous breakons en 004DCAA4. Traçons un peu en step over, remarquez le CharLower :

004DCADF |. 8B45 EC MOV EAX,[LOCAL.5]          ; serial dans EAX
004DCAE2 |. 8D55 FC LEA EDX,[LOCAL.1]          ; pointeur vers le Buffer de reception
004DCAE5 |. E8 02C5F2FF CALL RegFast.00408FEC  ; Char Lower

On se retrouve avec "59abcdefghijkl1111222223344445555".

La partie suivante du code retire les éventuels tirets du serial :

004DCAF6 |. BA 20CD4D00 MOV EDX,RegFast.004DCD20   ; => Suivez cette valeur dans le dump pour voir qu’elle vaut 0x2Dh
004DCAFB |. 8B45 FC MOV EAX,[LOCAL.1]
004DCAFE |. E8 8523F3FF CALL RegFast.0040EE88

Ensuite, on attaque les choses plus sérieuses. Les commentaires vous épargneront un long discours…

Regardons tout ça. D’une manière générale, les opérations se font de la manière suivante :

MOV EAX, [Adresse du Buffer de réception]
PUSH EAX Param 1 (nombre de caractères) Param 2 (Position dans la chaîne)
MOV EAX, [Chaîne à modifier]
CALL FONCTION

Pour visualiser le résultat d’un CALL, il vous suffit de suivre EAX dans le dump, sélectionner le DWORD qui s’y trouve, et faire un " Follow DWORD in dump".

Dans l’ordre, nous aurons les actions suivantes :

  • CharLower sur le serial entré.
  • Retrait des éventuels Tirets (0x2Dh).
  • Récupération de 8 caractères du serial à partir du neuvième. Appelons la Chaine1.
  • CharUpper sur Chaine1.
  • Récupération du 3ème caractère du serial entré, qu’on appelle Chaine2.
  • Récupération du 1er caractère du serial entré, qu’on appelle Chaine3.
  • Contrôle Chaine3 = valeur numérique, si c’est ok, on transforme Chaine3 en valeur décimal 35 h devient 5 d, 34 h -> 4 d ...
  • Placement de la valeur de Chaine3 dans un compteur. On le place dans ESI.
  • Incrémentation de 1 du compteur.
  • Récupération de 10 d(0A h) caractères dans une constante " e9847edf2c8d5d02d51c934d804469c5", à partir du ESIème caractère. Disons Chaine4.
  • Récupération du 2ème caractère du serial entré, qu’on appelle Chaine5.
  • Même opération que sur Chaine3. Placement dans ESI + incrémentation. Appelons le Compteur1. On laisse de côté.
  • Récupération de 5 caractères du serial entré à partir du 4ème, qu’on appelle Chaine6.
  • Vérification des valeurs non nulles des chaines récupérées. Si = 0, on saute vers Badboy.

Petite astuce : Vous pouvez définir les labels également sur les variables. Si vous souhaitez nommer les variables Chaine1, Chaine2, Chaine3, …, rendez vous à l’adresse en question dans le dump et appuyez sur la touche " : " pour personnaliser le label des variables :)

A ce niveau nous savons donc que notre serial devra faire au moins 8+ 9= 17 d caractères sans les tirets. Et que les 2 premiers caractères doivent être compris entre 0 et 9. Nous savons que nous avons à faire à une constante, car elle est visible dans les SDR avant même de lancer le soft. Elle n’est donc pas créée dynamiquement. Et c’est tout !

Continuons notre route…

004DCC06 |. 8D45 C4       LEA EAX,[LOCAL.15]
004DCC09 |. 50            PUSH EAX
004DCC0A |. FF75 F8       PUSH [LOCAL.2]          ; 8 caractères de notre serial qu'on a appelé Chaine1
004DCC0D |. FF75 F0       PUSH [LOCAL.4]          ; les 10 Caractères de la constante que l'on a appelé Chaine4
004DCC10 |. FF75 F4       PUSH [LOCAL.3]          ; 3eme char...qu'on a appelé Chaine2 ;)
004DCC13 |. 8D45 BC       LEA EAX,[LOCAL.17]      ; Placement de l'adresse du Buffer dans EAX
004DCC16 |. BA 03000000   MOV EDX,3               ; Nbre de chaines à concaténer
004DCC1B |. E8 8882F2FF   CALL RegFast.00404EA8   ; Concatenation

On poursuit les opérations avec une concaténation : Chaine7 = Chaine1Chaine4Chaine2

004DCC20 |. 8B45 BC       MOV EAX,[LOCAL.17]      ; Chaine7 dans EAX
004DCC23 |. 8D55 C0       LEA EDX,[LOCAL.16]      ; Nouveau Buffer dans EDX
004DCC26 |. E8 F995FDFF   CALL RegFast.004B6224   ; HASH MD5 de Chaine7

Chaine7 est donc aussitôt placée dans EAX. Puis un buffer vide est placé dans EDX. En regardant la valeur de sortie de fonction, on remarque tout de suite un hash type MD5 :

Stack SS:[0012F7A4]=016615E0, (ASCII "63fc3ba6906c959c546ca204277df1ec")
EAX=0012F774

D’une part, c’est exactement la même longueur qu’un hash MD5 (32 d caractères), de plus, Kanal nous avait sniffé, entre autres, du MD5. Et surtout, j’ai hashé Chaine7 en MD5 avec Keygenner Assistant, et j’ai eu le même résultat ;). La question est close !

Je sais pas pourquoi, mais je sens qu’on touche au but !

004DCC2B |. 8B45 C0        MOV EAX,[LOCAL.16]              ; Notre Hash dans EAX
004DCC2E |. B9 05000000    MOV ECX,5                       ; Nombre de caractères a récupérer
004DCC33 |. 8BD6            MOV EDX,ESI                     ; décalage dans le Hash de ESI qui vaut toujours Compteur1
004DCC35 |. E8 0E84F2FF    CALL RegFast.00405048
004DCC3A |. 8B55 C4        MOV EDX,[LOCAL.15]              ; On place dans EDX les 5 caractères récupérés
004DCC3D |. 8B45 FC        MOV EAX,[LOCAL.1]               ; On place notre Chaine6 dans EAX
004DCC40 |. E8 EF82F2FF    CALL RegFast.00404F34          ; Celui là, il ne serait pas bête d'y jeter un oeil ;)
004DCC45 |. 75 38          JNZ SHORT RegFast.004DCC7F     ; saute vers BadBoy si =! 0 :(
004DCC47 |. C683 30030000  MOV BYTE PTR DS:[EBX+330],1     ; Si on voulait cracker, on se pencherait sur cette variable ;)
004DCC4E |. 8D55 B4        LEA EDX,[LOCAL.19]
004DCC51 |. 8B83 F8020000  MOV EAX,DWORD PTR DS:[EBX+2F8]
004DCC57 |. E8 B0CDF8FF    CALL RegFast.00469A0C
004DCC5C |. 8B45 B4        MOV EAX,[LOCAL.19]
004DCC5F |. 8D55 B8        LEA EDX,[LOCAL.18]
004DCC62 |. E8 99C5F2FF    CALL RegFast.00409200
004DCC67 |. 8B45 B8        MOV EAX,[LOCAL.18]
004DCC6A |. E8 198E0400    CALL RegFast.00525A88
004DCC6F |. 8BC3           MOV EAX,EBX
004DCC71 |. E8 16010000    CALL RegFast.004DCD8C
004DCC76 |. 8BC3           MOV EAX,EBX
004DCC78 |. E8 53A0FAFF    CALL RegFast.00486CD0
004DCC7D |. EB 4E          JMP SHORT RegFast.004DCCCD     ; Saute au dessus des Badboy ;) , Surement vers le GoodBoy :)
004DCC7F |> 6A 30          PUSH 30
004DCC81 |. B9 50CD4D00    MOV ECX,RegFast.004DCD50       ; ASCII "Registry Fast"
004DCC86 |. BA 60CD4D00    MOV EDX,RegFast.004DCD60       ; ASCII "Invalid serial number! Please try again..."

On voit donc qu’on récupère 5 caractères dans notre hash à partir du ESIème caractère (qui vaut maintenant la valeur du 2ème caractère +1, notre Compteur1. En rentrant dans le CALL 00404F34, on voit bien que l’on compare la chaine extraite de notre hash avec Chaine6, soit les caractères 4 à 9 de notre serial entré. Récapitulons les dernières étapes :

  • Concaténation Chaine1 + Chaine4 + Chaine2 = Chaine7.
  • Hash MD5 de Chaine7.
  • Récupération dans le hash de 5 caractères à partir de Chaine5 + 1. Disons Chaine8.
  • Compare Chaine8 à Chaine6.

On teste donc notre nouveau serial : "59a06c95ghijkl1111222223344445555" :

>Et voili, et voilou ! :D

Nous en avons terminé avec celui-ci, vous avez tout ce qu’il vous faut pour faire un petit keygen.

PaPa Bango / ©2012 / http://xtx.a.nf/


MD5

---------------------------------------- Chapitre VII - Hachage ----------------------------------------

Deux exemples de protection basée sur des hash MD5 - 2/2

II - DEUXIEME CAS : Shadow Defender

Cible : Shadow Defender v1.1.0.331
Editeur : ShadowDefender.com

En plus d’être intéressant, il peut être pratique pour le reverse :). C’est un peu comme une machine virtuelle, mais c’est votre propre système qui est lancé sous la VM.

Une fois installé et redémarré, l’icône se trouve en barre des tâches. Quand on fait un clic droit sur "Open Shadow defender" un petit nag nous rappelle que c’est une version trial et nous propose de s’enregistrer. Voici notre boîte d’ enregistrement :

Premièrement, on remarque qu’on n’a pas le choix de la longueur, il suffit de remplir les cases. De même, le bouton "OK" ne semble s’activer qu’à la saisie d’un serial valide. Le contrôle doit donc se faire "en live".

Lancez Olly et attachez vous au processus defender.exe (ça évite de quitter et relancer ), n’oubliez pas de faire F9 pour relancer le soft après la pause à l’attachement.

Les SDR ne nous donnent rien de concret. Par contre, comme on a scanné avec PEiD et Kanal avant de commencer, on a un indice intéressant :

Il n’y a qu’une signature. Si c’est utilisé pour le serial, on tombera forcément dessus, c’est un bon point. Allons à cette adresse et remontons jusqu’au début de la routine en 0040FD68. On voit que celle-ci est appelée par 2 CALLs. Si on va à ceux-ci, on s'aperçoit qu'ils sont dans la même routine tous les deux, on va donc poser un BP en début de celle-ci ainsi qu'un label (touche " : "). Pour moi "HashMD5" :

On va aussi regarder du côté des imports (CTRL+N) et chercher "GetDlgItem", notre serial étant surement vérifié en direct :

Names in Defender, item 308 Address=004564EC Section=.rdata Type=Import (Known) Name=USER32.GetDlgItem

Un petit " Set Breakpoint on every reference" et ressaisissons la dernière lettre du serial pour voir si on est sur la bonne piste. On break directement sur un "GetDlgItem" ;), traçons gentiment en step over, tout en observant les registres et la pile, et nous voyons que les contrôles contenant notre serial sont récupérés :

0012CC24 01245730 UNICODE "abcde"
0012CC28 012470B0 UNICODE "fghij"
0012CC2C 01247FF0 UNICODE "klmno"
0012CC30 01248028 UNICODE "pqrst"
0012CC34 01248060 UNICODE "uVWXy

On continue de tracer, les différents CALLs recomposent le serial, on le suit via la pile, jusqu’à cette comparaison :

00410F52 |. 8378 F4 19   CMP DWORD PTR DS:[EAX-C],19
00410F56 |. 75 20        JNZ SHORT Defender.00410F78
00410F58 |. 6A 00        PUSH 0
00410F5A |. 50           PUSH EAX
00410F5B |. E8 2B330000  CALL Defender.0041428B
00410F60 |. 59           POP ECX
00410F61 |. 59           POP ECX
00410F62 |. 85C0         TEST EAX,EAX
00410F64 |. 74 12        JE SHORT Defender.00410F78

La longueur de notre serial reconstitué est comparée à 19 h (soit 25 d, la longueur à rentrer). Sinon on saute le CALL 0041428B, et on peut en profiter pour voir que si EAX vaut 0 à la sortie de ce CALL, on va au même endroit ;).

On continue avec F8 (et avec l’œil alerte ;)) jusqu’ici :

004142DF . E8 F04D0200    CALL Defender.004390D4
004142E4 . 8D85 9C000000  LEA EAX,DWORD PTR SS:[EBP+9C]   ; |
004142EA . 50             PUSH EAX                        ; |Arg1
004142EB . E8 633B0200    CALL Defender.00437E53          ; \Defender.00437E53
004142F0 . 83C4 1C        ADD ESP,1C
004142F3 . 33FF           XOR EDI,EDI
004142F5 . 83F8 19        CMP EAX,19
004142F8 . 0F85 A4000000  JNZ Defender.004143A2

Les 2 premiers CALLs font un CharUpper sur le serial, puis vérifient à nouveau la longueur.

On voit ensuite que notre serial est concaténé à la chaîne "V100" :

Ce qui me donne "ABCDEFGHIJKLMNOPQRSTUVWXYV100". On reprend le traçage, on voit que les 0Dh (13d) derniers caractères de la chaîne concaténée sont extraits (="QRSTUVWXYV100"), puis poussés en argument avant le CALL.HashMD5…mon label

Nul doute qu’on va breaker et que nous aurons le hash de note petite chaîne. Hashons celle-ci avec Keygenner Assistant par exemple, on obtient : "0FD5639B70DC1C22066D558AFA4A82CE".

On pose un petit BP en sortie du call et F9 jusqu’à revenir dessus, on continue de tracer et on arrive à cette boucle :

En suivant l’adresse du byte mis dans EAX, on tombe sur notre hash :

On va aller voir à l’adresse 004141B, en rentrant dans le CALL de la boucle. On remarque tout de suite un switch, qui va déterminer une valeur en fonction du byte placé dans EAX, au premier tour, pour OF h, on aura 53 h :

0041417B /$ 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4]
0041417F |. 6A 1F           PUSH 1F
00414181 |. 99              CDQ
00414182 |. 59              POP ECX
00414183 |. F7F9            IDIV ECX
00414185 |. 83FA 1E         CMP EDX,1E      ; Switch (cases 0..1E)
00414188 |. 77 7F           JA SHORT Defender.00414209
0041418A |. FF2495 0F4241   JMP DWORD PTR DS:[EDX*4+41420F]

Je ne vous colle pas tout le tableau du switch, mais voilà le petit calcul.

En sortie de switch, on voit que la valeur obtenue est comparée au premier caractère du serial entré. Si ce n’est pas égal, on sort de la boucle. On incrémente ESI, qui sert ici de compteur pour les caractères du hash et du serial, et on recommence la boucle 16 fois (0x010 h) avec les 16 bytes du hash, ce qui correspond aux 16 caractères restants de la chaîne concaténée : "ABCDEFGHIJKLMNOP".

Inversez le Flag Z à chaque passage sur le JNZ en 00414385 (sauf si par hasard, vous aviez la bonne valeur :)) pour pouvoir continuer à tracer.

On relève donc :

53 h=S,
36 h=6,
47 h=G,
41 h=A,
57 h=W,
44 h=D,37
h=7,
44 h=D,
47 h=G, 54
h=T,
32 h=2,
52 h=R, 43
h=C,
50 h=P,
47 h=G, 58
h=X

Soit "S6GAW-D7DGT-2RCPG-X"

Ajoutons-y la fin du premier serial entré "QRSTUVWXY", et réessayons après avoir retiré tous les BP avec : "S6GAW-D7DGT-2RCPG-XQRST-UVWXY"

Le bouton se dégrise, on valide et "It is Good! ;) ".

Voilà, ces deux petits exemples d’utilisation d’un hash MD5 sont terminés. On peut voir que pour ce dernier, même s’il n’y avait pas de SDR, ni de message d’erreur ou de réussite, le MD5 étant utilisé et étant la seule signature détectée, on pouvait facilement trouver un point d'entrée au milieu de la routine.

Pour en faire le keygen, on peut simplement "ripper" le switch pour ne pas s’embêter. Faut savoir être fainéant :D.

Je ne vous colle pas tout le code, vous n’allez pas le lire [image], mais la source est jointe ;).

PaPa Bango / ©2012 / http://xtx.a.nf/


Blowfish

------------------------------------- Chapitre VIII - Cryptographie -------------------------------------

Cryptographie symétrique : un exemple de Blowfish - 1/4

Cible : Nidesoft DVD Audio Ripper
Editeur : Nidesoft Studio (http://www.nidesoft.com/)
Protection : Blowfish

Outils nécessaires :
Crypto Searcher (by x3chun)
OllyDbg v1.10 (by Oleh Yuschuk)
Keygener Assitant v1.5 (by Mr Paradox & Prince)
Le plugin kanal de PEiD (by Jibz, Qwerton & snaker)

OS : Win XP

Introduction :

La cible est codée en Microsoft Visual C++ (MSVC) et utilise la librairie MFC.
Qu'est-ce que le MFC ? C'est une librairie, qui encapsule des portions de l'API Windows dans des classes C++.

Ceci implique qu'il faille tracer à l'intérieur de mfc42.dll pour voir notamment les appels aux API "classiques".

En scannant l'exécutable à l'aide du plugin kanal de PEiD, on trouve une référence à la S-Box (S pour substitution) de Blowfish :

Qu'est-ce que l'algorithme Blowfish ?

Afin de décrire correctement l'algorithme, je vais reprendre une grande partie du texte Blowfish Study 'n Reverse d'Evilcry en le traduisant (merci à lui).
Dans une moindre mesure, j'ai repris aussi quelques phrases du texte CryptoReversing d'Evilcry.
Pourquoi réinventer la roue à chaque fois ? ;)
Blowfish est un algorithme de chiffrement symétrique (i.e. « à clef secrète ») par blocs conçu par Bruce Schneier en 1993.

Qu'est-ce qu'un chiffrement symétrique (à clef secrète) ?

Un chiffrement symétrique utilise la même clef à la fois pour le chiffrement et le déchiffrement :

Le cracking / reversing de logiciels utilisant dans leur schéma d'enregistrement, des algorithmes de chiffrement symétrique, consiste donc à récupérer cette fameuse clef secrète et à savoir s'il y a chiffrement ou déchiffrement et ce qu'il a de chiffré / dé chiffré.
Par opposition, un chiffrement asymétrique (à clef publique), n'utilise pas la même clef pour le chiffrement et le déchiffrement :

Blowfish utilise une taille de bloc de 64 bits et la clé de longueur variable peut aller de 32 à 448 bytes.
Blowfish est basé sur un réseau de Feistel, utilisant itérativement une fonction de chiffrement 16 fois.
En plus du réseau de Feistel, Blowfish base son action sur 4 S-Boxes (peuvent être considérées comme des tableaux), indépendantes des clefs.
Il y a aussi une fonction OneWay F( ), qui n'est pas réversible.

La procédure de chiffrement peut être divisée en deux étapes :

Une phase d'extension / développement (ou fonction d'initialisation, qui est la phase la plus complexe) :

Dans cette phase, la clef, longue de 448 bytes au maximum, est "transformée" en tableaux de sous-clefs pour un total de 4168 bytes.

Une phase de chiffrement :

Le processus de chiffrement se déroule en 16 tours et utilise des opérations XOR et des additions.
La taille utilisée est le DWORD. Tout ceci est placé dans des tableaux indicés.
De la clef, sont extraites deux sortes de tableaux : P-Array (p pour permutation) et S-Box (s pour substitution).
Le P-Array est composé de 18 éléments et chaque sous-clef fait 4 bytes :

P1, P2, P3.....P18

Alors que les S-Box sont 4, chacune d'entre-elles est composée de 256 sous-clefs de 32 bytes.
Maintenant, voyons en détail, la phase d'extension :

1. Le P-Array et les 4 S-Boxes sont initialisées, la chose la plus importante à dire, est que, les 4 S-Boxes commencent toutes avec des valeurs constantes.
2. Il y a un XOR entre le P-Array et la clef choisie.
3. La procédure est répétée jusqu'à ce que toutes les sous-clefs du P-Array soient "codifiées" sous la forme :

P-Array = {xL1, xR1, xL2, xR2,......, xL18, xR18}

4. Avec ce nouveau "P-Array", commence le travail de substitution de la S-Box.
Note importante : ces S-Boxes sont modifiées à chaque tour, qui se produit.

Maintenant que nous savons un peu comment Blowfish travaille durant la phase de chiffrement, nous pouvons dire que Blowfish base son travail sur des S-Boxes aléatoires.
En fait, l'algorithme construit de grosses tables de recherche avec des données pseudo-aléatoires. Ce caractère "PSEUDOALEATOIRE" dépend fortement de la clef en elle-même.
Comme vous pouvez le voir, cette procédure est un peu complexe. Si nous essayions d'analyser les "implications", qui coexistent entre les S-Boxes obtenues et les clefs utilisées, nous verrions que ces relations sont vraiment complexes !

La conséquence directe de tout ceci, est que Blowfish semble ne pas être attaquable (excepté dans quelques cas) par cryptanalyse différentielle et linéaire. Les seuls attaques connues sur Blowfish fonctionnent essentiellement lors d'usage de clefs "faibles", dans lesquelles il y a au minimum une collision dans l'une des 4 S-Boxes.

Maintenant, voyons à quoi ressemble une routine de protection générique utilisant Blowfish :

▪ Blowfish_Initialization ( )
▪ Blowfish_encrypt ( )
▪ Blowfish_decrypt ( )

Ces fonctions dans Blowfish ont toutes comme paramètre, le context, une structure du type :

typedef struct {
unsigned long P[ 16+2 ] ;
unsigned long S[4][256] ; } BLOWFISH_CTX ;

Utilisée pour les S-Boxes et P-Boxes.

Nous pouvons maintenant passer à notre cible !

uLysse_31 / ©2011 / http://xtx.a.nf/


Blowfish

------------------------------------- Chapitre VIII - Cryptographie -------------------------------------

Cryptographie symétrique : un exemple de Blowfish - 2/4

Trouver un serial valide :

On lance le logiciel, on a la page d'accueil suivante :

Le logiciel demande un couple name / serial :

On va maintenant taper le couple email address (toto@gmail.com) / license code (0123456789).
On obtient alors l'erreur suivante :

En posant un breakpoint sur l'API MessageBoxA, on remonte facilement à l'appel dans mfc42.dll (encapsulation de l'API) :

puis à l'appel dans l'exécutable :

On voit donc bien que le paramètre (affecté à EAX) que l'on récupère en sortie de fonction doit être inférieur à 1869Fh sans être nul pour autant...
A quoi correspond ce dword affecté à EAX ?

Essayons d'abord de retrouver les différentes fonctions de Blowfish.

Phase d'extension (ou initialisation) :

En traçant jusqu'en 00416537, on finit par avoir ceci dans la pile :

Ça y est, on a notre clé privée !

Note : Attention, la clef privée peut ne pas être une chaîne.
On en a un exemple avec Drive Power Manager v1.10 :

On peut commenter le code pour le rendre plus compréhensible :

La taille de la clef (19h) a été déterminée par :

00416516 |. E8 81150200 CALL <JMP.&MFC42.#537>

Cet appel encapsule en fait l'API lstrlenA :

En traçant dans Blowfish_Init( ), on retombe sur l'initialisation des S-Boxes et du tableau P, qui sont des tableaux de valeurs constantes...
Les valeurs dans les S-Boxes sont tirées du nombre Pi exprimé en hexadécimal.
La routine suivante recopie en mémoire les 4 S-Boxes :

S-Box : 256 éléments (DWORDs)

Le premier élément de la 1ère S-Box est :

D1310BA6h

Il est notamment utilisé pour reconnaître la présence de Blowfish.
Il peut cependant arriver que certains programmes implémentent des versions modifiées de cet algorithme.
Ainsi, certains outils comme PEiD ou RDG peuvent échouer dans la détection, donc faites attention !

Voici, ensuite, la routine, qui effectue un XOR entre le P-Array et la clef choisie :

Si la taille de la clef (en bits) devient inférieur à l'index dans le P-array (18*32 bytes), on reprend le début de la clef...

P-array : 18 éléments (DWORDs)

Le premier élément du tableau P est :

243F6A88h

Voici le context (qui comprend le tableau P et les 4 S-Boxes) avant l'exécution de cette routine :

En vert, on a le 1er élément de la 1ère S-Box.

Le context devient après l'exécution de cette routine :

Dans la phase d'extension d'initialisation, on trouve des appels à la fonction de chiffrement ( Blowfish_encrypt ( ) ).
Cette source en C le confirme :

Voici les 2 appels à la fonction Blowfish_encrypt ( ) dans Blowfish_Initialization ( ) :

Dans Blowfish_Initialization ( ), le premier chiffrement par la fonction Blowfish_encrypt ( ), concerne le Tableau P, le deuxième, les 4 S-Boxes.
Il faut donc faire attention à ne pas confondre les appels à la fonction Blowfish_encrypt ( ) dans l'initialisation à un éventuel autre appel, qui, lui concernerait le chiffrement des données du serial.

Voici cette fonction Blowfish_encrypt ( ) :

Ici, on voit le réseau de Feistel à l'oeuvre ! On est au coeur de la fonction Blowfish_encrypt ( ).
On observe la valeur 10h (qui correspond aux 16 tours effectuées).
Le processus entier se base sur des opérations XOR, ces XORs se produisent entre deux données de 32 bits chacune.

On peut également remarquer que la fonction Blowfish_encrypt ( ) est appelée uniquement deux fois (par la fonction d'initialisation).

Ceci peut nous laisser émettre l'hypothèse que le serial contient des données, qui seront déchiffrées par Blowfish (la fonction Blowfish_decrypt ( ) ).
Le reverser devra donc chiffrer pour retrouver le bon serial.
Dans la plupart des cas, c'est cependant l'inverse, le programme utilise la fonction Blowfish_Init ( ) et la fonction Blowfish_encrypt ( ).
Le reverser n'aura donc qu'à connaître la clef secrète et à implémenter une fonction de déchiffrement...

Voici la fonction GetByte (X,Y), appelée par Blowfish_encrypt ( ) :

La fonction de déchiffrement est appelée deux fois dans l'initialisation et s'applique au Tableau P, puis aux 4 S-Boxes.
La fonction Blowfish_Init( ) s'achève alors. On peut donc passer à la phase suivante...

uLysse_31 / ©2011 / http://xtx.a.nf/


Blowfish

------------------------------------- Chapitre VIII - Cryptographie -------------------------------------

Cryptographie symétrique : un exemple de Blowfish - 3/4

Phase de déchiffrement :

On peut déjà remarquer que la routine d'enregistrement ne tient compte que du serial entré...

Au niveau de cette routine :

On obtient au niveau de la pile :

Arg1 pointe vers le serial.
Arg2 pointe vers :

C'est là où sera stocké le serial.
Par la suite, on peut reconnaître DataL / DataR.

C'est ce que demande le serial, qui doit donc représenter 2 DWORDs et faire 16 caractères...

Voici la routine qui récupère le "1er DWORD" :

Dans la pile, on a :

En 000F2794, on voit apparaître :

Voici l'appel à la fonction Blowfish_decrypt ( ) :

Dans la pile, on récupère les paramètres passés à la fonction :

En sortie (valeurs retournées), on récupère :

On peut donc retrouver le serial déchiffré, à l'aide de Keygener Assistant d'AT4RE :

Après déchiffrement du serial, on a deux vérifications :

Voici la fonction Blowfish_decrypt ( ) :

avec le même appel à la fonction GetByte (X,Y) :

Essayons de trouver un serial valide :

Input :

000000010001869E

Serial :

6223E492C4F077C0

La 1ère partie du serial est quelconque, la deuxième partie de serial doit être non nulle mais inférieure à 1869Fh.

Le serial calculé précédemment est finalement validé par un message :

Le code correspondant est :

L'API MessageBoxA est elle aussi encapsulée :

Où est stocké le serial ?

Il est dans un keyfile.

Dans la pile, on trouve le chemin d'un fichier au nom anodin :

Il s'agit du fichier bitmap, qui apparaît lors de l'analyse du DVD inséré :

Le serial est stocké à la fin du fichier en question :

Avant enregistrement, on a :

Après enregistrement :

On peut passer au keygenning :).

uLysse_31 / ©2011 / http://xtx.a.nf/


Blowfish

------------------------------------- Chapitre VIII - Cryptographie -------------------------------------

Cryptographie symétrique : un exemple de Blowfish - 4/4

Keygenning :

La 1ère partie du serial est quelconque, la deuxième partie de serial doit être non nulle mais inférieure à 1869Fh.
La 1ère partie du serial (1er DWORD) étant aléatoire, on peut se demander s'il n'est pas vérifié par la suite (hidden check).
En mettant un hardware breakpoint on read, on s'aperçoit qu'il est finalement écrasé dans la pile...
Il faut donc générer un nombre aléatoire de 8 chiffres, suivi d'un deuxième nombre aléatoire de 8 chiffres, non nul mais inférieur à 1869Fh.
Ensuite, il suffit de chiffrer à l'aide de la fonction Blowfish_encrypt ( ) et de la clef privée, ce nombre de 16 chiffres, qui est dans un format hexadécimal.

Voilà, il nous reste plus qu'à coder :).

Coding :

On va réaliser la programmation du keygen en C.

J'utilise l'IDE Codeblocks et GCC Compiler.
Pour retoucher les ressources (fichiers .rc), j'utilise Resedit.
Dans les templates, j'utilise des images BITMAP (avec l'option RLE Compressed via HyperSnap-DX 4), afin de gagner de la place...

Pour l'implémentation du Blowfish, j'ai repris ces sources : http://www.schneier.com/code/bfsh-koc.zip

D'autres sources sont disponibles sur : http://www.schneier.com/blowfish-download.html.

Le calcul du Blowfish se fait dans main.cpp à ce niveau là :

Les sources sont fournies (elles sont loin d'être super propres :P).

Conclusion :

Blowfish reste un algorithme accessible.
Dans la majorité des cas (ce n'est pas le cas ici Description : :P), les schémas d'enregistrement utilisent la fonction Blowfish_Init ( ) et Blowfish_encrypt ( ), ce qui facilite le travail du reverser.
Il a juste à implémenter une fonction de déchiffrement et a besoin de connaître la clef (habituellement sniffée en mémoire).
On en a un exemple avec Drive Power Manager v1.10.

Moins fréquemment, le programme inverse les fonctions, et donc le programme implémente l'initialisation et le déchiffrement, et le reverser aura besoin de celle du chiffrement.
Il faut donc faire attention aux fonctions que l'on a réellement.

Le pire cas que l'on peut trouver est un Blowfish modifié, en d'autres mots, les protections qui n'implémentent pas la forme canonique de Blowfish, mais une version qui change quelques-uns de ses aspects. Habituellement, les modifications que l'on rencontre ne sont pas nombreuses. Voici les plus courantes :

  • Un nombre altéré de tours dans le réseau de Feistel (32, couramment).
  • Des S-Boxes avec des valeurs différentes, ce qui constitue un moyen pour échapper à la détection des outils de recherche automatisée d'algorithmes.
  • La fonction F est modifiée et retourne fréquemment S[2][d]-S[1][b])^(S[0][c]+S[3][a]

Si vous voulez tester Blowfish, vous pouvez vous entraîner également sur :
WebExpert v6.60
Drive Power Manager v1.10

Le keygenning de cette application reste finalement simple :).
Le schéma d'enregistrement est d'ailleurs le même pour tous les logiciels de cette boîte, à l'exception de Nidesoft DVD Copy v5.2, qui utilise un ancien schéma d'enregistrement (toujours avec Blowfish), mais il présenterait un bug...

Merci de ne pas utiliser ce tutoriel et les sources jointes, pour sortir un keygen en 0day ou ailleurs !

Quant aux logiciels, ils ont un intérêt limité.
Ça doit être encore un de ces auteurs qui pompent et adaptent des sources de logiciels libres pour se faire de l'argent ...

uLysse_31 / ©2011 / http://xtx.a.nf/


RSA

------------------------------------- Chapitre VIII - Cryptographie -------------------------------------

RSA - exemple de chiffrement asymétrique simple.

Outils nécessaires :

OllyDbg (by Oleh Yuschuk)
PEiD et son plugin kanal (by Jibz, Qwerton & snaker) ou tout autre analyseur d'exécutables !
rsab0x (by Team RED)
RSA-Tool 2 (by tE!)

- Les notions de base des précédents cours sur l’assembleur et l’utilisation d’Ollydbg sont considérées acquises.

Cible :

Plato DVD Ripper v12.12.01
Editeur : Plato Software Inc

Je dis simple bien que j’ai mis un certain temps à le comprendre :), mais sur les différentes cibles que j’ai approchées, celle-ci fait partie des cas simples. Donc merci uLysse de me l’avoir indiqué depuis quelque temps, je l’ai ressortie du placard ;).

Tout d'abord, vous pouvez, ou plutôt vous DEVEZ aller lire cet article, qui explique le fonctionnement global du RSA : https://en.wikipedia.org/wiki/RSA_%28algorithm%29 ou en version française : http://fr.wikipedia.org/wiki/Rivest_Shamir_Adleman

Notre cible n’est plus toute jeune et a dû être déjà keygennée :o.

Pour les outils, je vous conseillerais d’utiliser rsab0x de la team RED au lieu de Keygenner assistant, parce que j’ai rencontré quelques bogues avec ce dernier (ça reste un outil très pratique, même s’il est un peu bogué), par exemple :

Bref, on commence par scanner un petit coup avec PEiD et le plugin Kanal. On voit l’utilisation de Miracl, une librairie de BigNum. On n’est pas certain qu’elle soit là pour du RSA, mais c’est déjà une indication pour une éventuelle utilisation.

Chargeons la cible dans le debogueur, lancez la et rentrez les infos d’enregistrement :

Ce qui nous donne :

Une petite recherche de strings, on ne trouve qu’une seule occurrence de « Invalid license name… » :

String Invalid ...

On va naturellement aller regarder ce qui se passe dans le CALL 004162D0 Clin d'oeil. On remarque tout de suite qu’on sort rapidement de la routine si EAX ne vaut pas 30 h (48 d) :

004162FF |. 83F8 30  CMP EAX,30
00416302 |. 74 19    JE SHORT dvdrippe.0041631D

Pour la prochaine fois, rentrons directement un serial de 30h caractères :) , et posons un petit BP en 004162FF. Il s’agit de la comparaison de la longueur du serial avec 30 h, continuons à tracer :

On remarque ici que le serial est coupé en 3 parties distinctes de 16 d (10 h) caractères. Puis on tombe, sur 3 strings qui pourraient être des bignums :). La même fonction est appelée chaque fois après qu’ils soient poussés en argument. Notez au passage que la valeur d’EAX en sortie du CALL dvdrippe.00416170 sera déterminante pour la validation du serial. EAX est stocké à l’issue de chaque fonction :

Ici : 004163D2 |. 8BF0 MOV ESI,EAX pour le premier.
En : 004163D9 |. 8BF8 MOV EDI,EAX pour le second.

et le dernier restera dans EAX.

Ci-dessous, on comprend que la valeur d’eax devra être égale à la somme de EDI + ESI, soit la somme des valeurs d’EAX en sortie des 2 premiers CALL 00416170.

0041640A |. 03FE    ADD EDI,ESI
0041640C |. 33C9    XOR ECX,ECX
0041640E |. 3BF8    CMP EDI,EAX
00416410 |. 0F94C1  SETE CL
00416413 |. 5F      POP EDI
00416414 |. 5E      POP ESI
00416415 |. 5B      POP EBX
00416416 |. 8BC1    MOV EAX,ECX

Allons faire un tour dans le CALL 00416170, et descendez quelque peu dans le code. Cette ligne doit vous sauter aux yeux :

0041624C |. 68 B8A14700  PUSH dvdrippe.0047A1B8   ; ASCII "10001"

Cette valeur, 10001 h ou 65537 d est typique du RSA et est couramment utilisée comme clé publique E.

Nous pouvons maintenant penser que nous avons trois N dont les deux premiers sont identiques, que la clé publique sera commune, et comme chacune de nos parties de serial découpé sont respectivement poussées dans l’ordre, en argument avant chaque call, on peut imaginer que chaque partie sera le texte à chiffrer (M) selon la formule suivante : C = M^E(mod N).

Nous aurons 3 chiffrements RSA successifs. Faisons un essai pour s’en assurer.

On pose :

  • E = 10001
  • N = A6AF9BBA070DA0F9
  • M = 1ère partie du serial
  • E = 10001
  • N = A6AF9BBA070DA0F9
  • M = 2ème partie du serial
  • E = 10001
  • N = 7595976322BEEF99
  • M = 3ème partie du serial

Nous allons donc déchiffrer trois valeurs pour tester cette hypothèse, et il sera plus facile de suivre nos valeurs une fois chiffrées :).
Pour cela il nous faut calculer la clef privée D de chaque modulo découvert (le couple N/E formant la clef publique). Lancez Rsa-Tool 2, puis cliquez sur "Exact size"(1) si vous voulez connaître la taille de la clef.
Ensuite il vous faudra retrouver les nombres premiers P et Q en cliquant sur "Factor N"(2) puis il ne reste plus qu’à calculer D (3) .
Dans notre cas, avec du 64 bits, c’est très rapide. Mais cela peut s’avérer extrêmement long avec des clefs de grandes tailles.
N’oubliez pas de vérifier que vous êtes bien en base16.

Notez la clef privée : (celle des 2 premiers modulos) :

  • D= 3CCC10A04E095181

Puis recommencez avec le 3ème modulo qui sera utilisé pour la 3ème partie du serial :

  • D= 3B3A2D75673DEEED

Je vais déchiffrer les 3 valeurs suivantes, selon la formule M = C^D (Mod N) pour composer mon fake serial (utiliser rsab0x pour éviter les surprises ;)). Si le M obtenu ne fait pas 16d caractères, ajoutez un 0 devant. Pour obtenir des valeurs déchiffrées correspondantes en ASCII, il nous faut encoder leurs valeurs en hexa :

11111 =hexEnc=> 3131313131 =RSADechif=> 3559D94398FD3C3F
22222 =hexEnc=> 3232323232 =RSADechif=> 36916D678A42F0D2
33333 =hexEnc=> 3333333333 =RSADechif=> 4614C5D25C379B5A

=> 3559D94398FD3C3F36916D678A42F0D24614C5D25C379B5A

On retourne essayer ce serial, il fait bien 30 h, donc on continue… le serial est découpé puis la première partie est poussée avec le premier modulo N, donc rentrons dans ce CALL, et traçons jusqu’ici :

00416210 |> 0FBE1437        /MOVSX EDX,BYTE PTR DS:[EDI+ESI]
00416214 |. |52             |PUSH EDX
00416215 |. |E8 7F070400    |CALL <dvdrippe.IsHexNumber>       ; verifie si chaque caractere de la chaine est sur la plage HEX (0-9)(A-F)
0041621A |. |83C4 04        |ADD ESP,4
0041621D |. |85C0           |TEST EAX,EAX
0041621F |. |0F84 80000000  |JE dvdrippe.004162A5              ; sinon, on degage, pas d'encryption :(
00416225 |. |83C6 01        |ADD ESI,1
00416228 |. |3B7424 10      |CMP ESI,DWORD PTR SS:[ESP+10]
0041622C |.^\7C E2          \JL SHORT dvdrippe.00416210

Cette boucle vérifie que la chaîne à chiffrer ne contient que des caractères hexadécimaux, c’est assez logique…, sinon on saute.

On continue de tracer, et on arrive là :

En suivant EBX dans le dump, on retrouve notre chaîne à chiffrer :

Puis on retrouve la chaîne déchiffrée en suivant ESI après la fonction :

Dans le dump on lit 3559D94398FD3C3F sous la forme inversée 3F 3C FD 98 43 D9 59 35, idem pour 3131313131 , mais là ça ne se voit pas :D.

On continue de tracer pour tenter de voir ce qui détermine EAX, jusqu’au PUSH EDX, on suit EDX dans le dump, et on tombe sur notre chaîne déchiffrée :

Tracez en step over par-dessus la fonction, regardez EAX à la sortie, on a EAX = 2B67 h :). Soit la conversion en hexa de 11111 d. On sort de la routine et on remarque en 004163D2, que 2B67 h est stocké dans ESI.

Continuez ainsi avec les 2 autres chaines. On aura 56CE h pour la 2ème chaîne qui sera placée dans EDI , puis 8235 h sera dans EAX pour la 3ème chaîne, en arrivant ici :

0041640A |. 03FE    ADD EDI,ESI
0041640C |. 33C9    XOR ECX,ECX
0041640E |. 3BF8    CMP EDI,EAX
00416410 |. 0F94C1  SETE CL
00416413 |. 5F      POP EDI
00416414 |. 5E      POP ESI
00416415 |. 5B      POP EBX
00416416 |. 8BC1    MOV EAX,ECX

Vous remarquez tout de suite qu’on ajoute ESI à EDI avant de comparer cette somme avec EAX. Il faut donc que la valeur ASCII de la 3ème chaîne chiffrée avec le 2ème modulo, soit égale à la somme des valeurs ASCII des deux autres chaînes chiffrées avec le 1er modulo, pour que le serial soit valide.

Dans notre cas, pas besoin de faire le test…11111 d+22222 d=33333 d :), donc le serial doit être valide :), et ce n’est même pas volontaire :D. Notez qu’en cas d’échec de la conversion décimale en hex, la valeur d’EAX sera à zéro, et la condition ne sera pas vérifiée…c’est ce qui m’a un peu gêné au début, car je déchiffrais 11111 au lieu de 3131313131 h, donc la valeur ASCII n’était pas convertissable en hex, puisque non décimale [image] ... On ne prendra donc que des chiffres pour la génération aléatoire de valeurs lors de la création du keygen.

Je dois quand même vous parler des méthodes pour repérer l’utilisation de librairie de crypto. Même si dans ce cas, PEiD repère la librairie Miracl, ce n’est pas toujours le cas… J’aime bien charger la cible dans IDA, appliquer les différentes signatures habituellement utilisées comme FGInt, BigLib ou le pack de signatures de dihux.

Vous pouvez ensuite produire un fichier .map que vous pourrez importer aisément dans Olly, à l’aide du plugin GODUP. Ce dernier permet même d’importer directement des signatures IDA.

Mais là n’est pas le sujet du tuto ;).

Un petit merci particulier à uLysse_31 pour sa patience et son aide.

La source du keygen est inclue dans le pack, j’ai simplement modifié un template récupéré sur l’excellent BLOG de xylitol.

Pour compiler la source, il vous faut Masm et WinASM.

PaPa Bango / @2012 / http://xtx.a.nf/


XTX

---------------------------------------- Chapitre IX - Les packers ----------------------------------------

UPX

Cible :

Testupx

Outils nécessaires :

OllyDbg (by Oleh Yuschuk)
Imprec
Lord-Pe
Peid 0.95

(Tous les programmes et outils de ce tutoriel sont stockés dans le répertoire Pack).

1 - Introduction

  • Qu'est-ce qu'un packer ?

C'est un outil qui permet à la fois de compresser (un peu comme le ferait winzip ou winrar) un fichier exécutable ou une dll et qui souvent le sécurise en rajoutant différentes protections. Le fichier obtenu conserve son type (.exe ou .dll) et reste ainsi utilisable comme à l'origine.

  • Comment ça marche ?

Pour simplifier on ne parlera que d'exécutable (.exe) sachant que le principe est le même dans le cas d'une bibliothèque (.dll).

Le packer va créer un nouvel exécutable dans lequel il va intégrer une version compressée de l'exe d'origine ainsi qu'un loader. Le loader (chargeur en français) est une routine qui va permettre de décompresser l'exécutable d'origine en mémoire lorsque l'on lancera le programme packé. Ainsi, le programme d'origine sera uniquement lisible en mémoire. Si on ouvre la version packée présente sur notre disque avec un debugger, seule la routine du loader sera compréhensible, le reste étant compressé, nous ne pourrons pas l'analyser. Il nous faut donc recopier l'exécutable d'origine à partir de la mémoire. Puis le rendre fonctionnel. On parle dans ce cas d'unpacking. Mais ce n'est pas aussi simple qu'il n'y parait ...

Pour aborder ce chapitre, il faut tout d'abord quelques bases sur la structure d'un fichier exécutable.

  • Le format PE

Tous les programmes destinés à tourner sous Windows doivent respecter une norme : il s'agit du format PE ( Portable Executable) qui établit des règles strictes sur l'organisation des différentes parties d'un programme. Nous n'allons pas rentrer dans le détail mais juste voir de façon simplifiée quelques points pour vous permettre de comprendre la suite du tuto.

  • Le PE Header

C'est l'en-tête du programme, pour ainsi dire le sommaire. C'est lui qui va donner les indications permettant à Windows de charger correctement l'exécutable. On va par exemple trouver dans le PE HEADER l'adresse de la première instruction à exécuter (l'EP, Entry Point, point d'entrée), l'adresse des différentes sections, quel compilateur a été utilisé, etc ...

  • Les sections

Ce sont en quelques sortes les paragraphes du programme. Par exemple :

.text : le code
.data : les variables initialisées
.rdata : les données en lecture seule
.rsrc : les ressources (images, sons ...)

L'adresse de chaque section se retrouve dans le PE Header.

  • L'Entry Point (EP)

C'est l'adresse de la première instruction (code) que doit exécuter Windows dès que le programme a été chargé. Lorsqu'un exécutable a été packé, c'est l'adresse de début du loader. Le point d'entrée de l'exécutable décompressé en mémoire devient l'OEP ( Original Entry Point).

  • L' Import Address Table ( IAT)

Vous avez pu voir lors des précédents cours qu'un exécutable, lors de son exécution, fait appel aux fonctions des dlls (les APIs) de Windows (GetWindowText, GetDlgItemText ...). Mais comment fait-il pour savoir où se trouvent ces fonctions ? En fait il n'en sait rien. Lors de la compilation du projet, toutes les fonctions nécessaires au programme sont listées dans un tableau appelé IAT :

004xxxxx GetWindowText
004xxxxy GetDlgItemText
004xxxxz ...

Lors du chargement du programme, Windows va lire ce tableau et remplacer les noms des fonctions par leurs adresses. L'exécutable fait ses calls vers les adresses de son IAT et le call est alors redirigé vers l'adresse créée par Windows.

  • L' ImageBase

C'est l'adresse à laquelle est chargé le programme, en règle générale 400 000h.

Cette introduction a pour seul but de vous apporter un minimum de compréhension sur les termes employés lorsque l'on aborde l'unpacking. Si vous voulez approfondir ce sujet, je vous invite à faire vos propres recherches sur le net, la documentation est nombreuse et le sujet est vaste !

2 - Observations

Il faut savoir qu'UPX n'est pas à proprement parler un packer mais un simple compresseur d'exécutable qui n'apporte aucune protection.
Certes il empêche de voir les strings datas, de lire le code du programme original, modifie le PE header et renomme/crée ses propres sections nommées UPX0 et UPX1 mais il n'ajoute aucune sécurité supplémentaire.

Regardons un screenshot de la cible dans Peid :

3 - Fonctionnement basique d'un packer:

Un packer/compresseur va décompresser le programme en mémoire et seulement une fois sa routine de décompression terminée, donner la main à l'exécutable virtuel ainsi créé.

Dans ce cas-ci, Upx, comme les autres packers/compresseurs, aura son Entry-Point à un offset différent de celui du programme original.

Le but est donc de retrouver l'Entry-point du programme original avant qu'il n'ait été compressé par Upx.

Upx a une signature très simple dans son fonctionnement : il commence toujours par un PUSHAD et se termine toujours par un POPAD et ce y compris la dernière version du jour, la 3.09 !

Il va pousser sur la pile le contenu de tous les registres afin d'exécuter la décompression du programme original en mémoire et remettre les registres comme ils étaient avec son POPAD une fois terminé pour ensuite utiliser un JMP vers l'Original Entry-Point de la cible décompressée en mémoire !

4 - Lançons la cible dans Ollydbg :

Si comme moi vous tombez sur l'écran juste ci dessous, vous remarquerez que vous n'êtes pas dans la cible mais dans une dll (1). Pour aller dans la cible à analyser, cliquer sur le bouton Alt+E (2) :

puis double-cliquez sur la ligne de votre cible comme indiquée par la flèche sur l'image ci-dessous :

Vous vous retrouvez enfin au bon endroit c'est à dire à l'Entry-Point d'UPX et sa fameuse première instruction PUSHAD :

Sachant qu'UPX commence par un PUSHAD et se termine par un POPAD, cherchons ce fameux POPAD en scrollant vers le bas comme dans l'image ci-dessous :

Nous nous retrouvons ici :

Vous voyez la ligne avec le POPAD (flèche 1) et 6 lignes plus bas un JMP vers un offset... (Flèche 2).

Sélectionnons la ligne du JMP puis appuyons sur F2 afin de mettre un breakpoint juste à la fin de la décompression d'UPX :

Une fois ceci fait, vous avez l'offset surligné en rouge pour vous indiquer qu'il y a un breakpoint sur cette ligne !

Lançons Olly en pressant F9 afin qu'il breake sur la ligne que nous lui avons demandé :

Appuyons sur F8 afin d'exécuter le JMP et nous atterrissons à l'Entry-Point Original ou OEP !

Maintenant c'est bien, on est à l'OEP de la cible, mais on a toujours pas de fichier fonctionnel, tout ce qui est ici se trouve uniquement en mémoire :-(

Nous allons pour ce faire, dumper le fichier décompressé (créer une copie/sauvegarde sur le disque) et reconstruire les imports (IAT) car ceux-ci correspondent aux offsets d'UPX !

Avec la version 2 d'Ollydbg, il n'y a pas encore de plugin pour dumper directement comme dans la version 1, mais nous allons utiliser Lord-Pe.

Une fois LordPe ouvert, sélectionner notre fichier cible, puis clic droit ensuite cliquez sur dump full !

Si tout s'est bien passé, vous avez un fichier nommé dumped.exe sur votre disque dur, ce fichier n'est pas encore valide car les imports sont encore dirigés vers les offsets UPX. Pour terminer le travail correctement et avoir un fichier valide, il faut rediriger les imports avec Imprec, programme qui fait quasi tout le boulot à notre place !

Ouvrez donc Imprec et sélectionnez notre fichier cible :

Une fois fait, nous arrivons ici et nous voyons que notre imagebase n'est pas bonne car elle contient les informations d'UPX, or nous voulons utiliser les imports de la cible originale !

Si vous regardez dans Olly, l'OEP où se trouve votre PUSHAD est 4600F0h c'est l'OEP du programme compressé 400000h+600F0h

Nous on veut remettre l'OEP du programme original à savoir 401000h donc 400000h+1000h, nous devons donc remplacer 600F0h par 1000h :

Une fois le bon OEP entré, nous cliquons sur Iatautosearch pour voir s'il trouve quelque chose ...

Bingo il a trouvé, on peut donc cliquer sur ok afin de valider !

Après avoir cliqué sur IATautoSearch (1)

Maintenant il faut cliquer sur getimports (2) pour voir s'il va nous trouver les imports en rapport avec notre OEP.

Il en a trouvé 3 et il n'y a pas d'erreur, ils sont tous les 3 valides (3).

Si vous avez des doutes, vous pouvez vérifier en cliquant sur showinvalid, mais il restera comme cela vu que tous les imports sont tous bons.

Nous pouvons donc fixer notre dump (4) en cliquant sur le bouton fix dump et en indiquant l'emplacement de notre fameux fichier dumpé avec LOrdPe !

Si tout s'est bien passé, vous devriez avoir la même ligne bleue que moi, saved succesfully !

Vous devriez avoir un fichier dumped_.exe valide, c'est à dire qu'il est fonctionnel comme le fichier cible de départ , avec le compresseur Upx en moins !

Ce qui veut dire que votre fichier est prêt à être craqué !

Un scan du fichier cible avec Peid avant de commencer, on voit bien qu'il est compressé avec Upx :

Un petit scan avec Peid après notre unpacking pour voir si UPX est bien retiré :

On peut constater que Peid nous indique un autre packer, il s'agit d'une fausse signature réalisée par Goldocrack lors de la création de son crackme, mais on voit en tout cas qu'Upx n'est plus !

J'espère que vous avez appris quelque chose, UPX étant une protection plus que simple !

Coolmen / @2013 / http://xtx.a.nf/


XTX

---------------------------------------- Chapitre IX - Les packers ----------------------------------------

ASPACK

Cible :

Goldo8

Outils nécessaires :

OllyDbg (by Oleh Yuschuk)
ChimpRec
Peid 0.95

(Tous les programmes et outils de ce tutoriel sont stockés dans le répertoire Pack).

1 - Observations

Il faut savoir qu'Aspack tout comme Upx n'est qu' un compresseur d'exécutable c'est à dire un programme qui réduit la taille d'un fichier qu'il soit .exe ou .dll. Certes il empêche de voir les strings datas, modifie le PE header et ajoute 2 sections nommées .adata et .aspack.

Regardons sa signature dans Peid (1) :

On remarque que l'Entry- Point se trouve à l'offset 5F001h et qu'il se trouve dans la section .aspack (2) alors qu'en général un fichier non compressé a son Entry-Point à l'offset 100h0 et se trouve dans la section .text.

Regardons un screenshot de la cible dans Peid :

2 - Même principe de base qu'Upx

Aspack tout comme Upx commence toujours par un PUSHAD et se termine toujours par un POPAD et ce y compris la dernière version du jour, la 2.29, que j'ai utilisée pour compresser la cible !
Il va pousser sur la pile le contenu de tous les registres afin d'exécuter la décompression du programme original en mémoire et remettre les registres comme ils étaient avec son POPAD une fois terminé pour ensuite utiliser un JMP vers l'Entry-Point de la cible décompressée en mémoire !
Sauf qu'à la différence d'Upx, Aspack PUSH l' adresse de l'OEP sur la pile directement suivi par un RET ce qui en programmation assembleur équivaut à un JMP !

Une petite image vaut mieux qu'un long discours ;-)

3 - Lançons la cible dans Ollydbg :

Toujours vous assurer avant de faire quoi que ce soit, que vous êtes bien dans le module exécutable et non ailleurs !

Si vous voulez ne plus être embêté avec ce genre de cible, il vous suffit d'aller dans Olly, onglet option/rubrique, debugging/start et vous cochez comme ci-dessous, vous faites OK, redémarrez Olly et vous tomberez directement sur le point d'entrée actuel de votre cible !

Lançons notre cible dans Olly et nous sommes enfin au bon endroit c'est à dire l'Entry-Point d'Aspack et son PUSHAD :

Vous remarquerez que j'ai coupé la vue de Olly afin de diminuer la taille en octets des images et de réduire la taille du tutoriel global car si tout le monde met des grands screen-shots, on va vite se retrouver avec un tutoriel de plusieurs centaines de méga à télécharger !

Sachant qu'Aspack comme UPX commence par un PUSHAD et se termine par un POPAD, cherchons ce fameux POPAD sauf qu'ici nous allons y arriver par une autre technique qui fonctionne pour quasi tout les packers basiques, même pour UPX, sissi :-)

Regardez l'image ci-dessus et plus précisément la valeur du registre ESP qui actuellement a pour valeur 18FF8Ch.

Nous savons que les instructions Pushad/Popad servent d'abord à sauver les registres tels qu'ils sont avant la décompression par le packer puis de les restaurer à leur état initial après que le packer ait terminé son job et donne la main à la cible .

Nous allons donc faire une seule instruction afin que Aspack modifie les registres, puis poser un Hardware breakpoint sur la valeur dword du registre ESP afin qu'une fois que le packer veuille les restaurer, Olly breake juste avant et que nous puissions arriver à l' OEP de la cible décompressée !

Ce qui, je le rappelle, est le but de ce tutoriel.... ben oui on ne sait jamais, des fois que vous auriez oublié !!!

Bon on appuie sur F7 et on constate que la valeur d'ESP a changé , on clique donc droit sur ESP puis sur follow in dump :

Nous voyons bien que nous sommes à l'adresse d'ESP dans la fenêtre de dump de Olly.

Nous allons poser un Hardware BreakPoint sur la valeur Dword de ESP, pour ce faire vous sélectionnez les 4 doubles 0 avec votre souris puis clic droit, breakpoint/hardware.

Vous laissez coché on Access R/W, Dword et la 3è empty, Olly y mettra la valeur automatiquement !

Clic sur OK !

Une fois le Hardware BreakPoint placé, vous avez l'offset qui est en rouge pour vous indiquer qu'il y a un breakpoint sur cette ligne !

Une fois posé lançons Olly en pressant F9 afin qu'il breake comme nous lui avons demandé !

Nous constatons 3 choses :

1) Olly s'est bien arrêté suite à notre breakpoint (1)
2) On est bien à la fin de la routine de décompression d'Aspack, voir le POPAD (2)
3) Le POPAD a bien remis la valeur d'ESP d'origine, voir valeur plus haut avant notre F7 (3)

Maintenant qu'Olly a breaké, appuyons sur F7 jusqu'au RETN + 1fois F7 et nous atterrissons à l'Entry-Point original ou OEP du programme décompressé !

Contrairement à mon tut avant celui-ci sur UPX, pour dumper et fixer les imports, je vais utiliser un seul programme qui fait tout au lieu de LordPe et ImpRec. Ce programme s'appelle ChimpRec !

Lançons le afin de sauver/dumper notre cible décompressée sur le disque dur !

1) Sélectionnons notre fichier cible (1)
2) Cliquons sur dump pour le sauver sur le disque dur (2)

Si tout s'est bien passé, vous avez un fichier nommé goldo8-.exe sur votre disque dur, ce fichier n'est pas encore fonctionnel car les imports sont encore dirigés vers les sections Aspack et sont donc invalides !

Pour terminer le travail correctement et avoir un fichier valide, il faut rediriger les imports.

Nous sommes toujours dans ChimpRec, cette fois nous allons remettre la bonne valeur de notre OEP et non celle d'Aspack !

Remember :

OEP Aspack : 45F001h - 400000h (imagebase) = 5F001h (regardez la valeur OEP RVA dans le bas de l'image ci dessus, c'est exactement çà !)
OEP cible originale avant Aspack : 401000h - 400000h ( imagebase) = 1000h, c'est la valeur que nous devons mettre dans OEP RVA de ChimpRec !

Une fois fait, cliquons sur IAT AutoSearch, normalement il doit trouver quelque chose et cliquons sur OK !

ChimpRec nous confirme, et nous dit d'essayer GetImports, ce que nous allons faire !

1) On voit que tous les imports sont résolus (1)
2) 0 unresolved pointers, confirme que tout est ok ! (2)

Il n'y a plus qu'à fixer notre dump !

Lorsque vous cliquez sur FixDump, sélectionnez bien votre fichier dump et non le fichier original ou autre !!!

Par défaut, ChimpRec reprend le fichier dump qu'il a créé mais vérifiez bien que ce soit le bon !

Une fois OK :

ChimpRec vous a créé un 3eme exécutable cette fois fonctionnel et sans Aspack.

Si tout s'est bien passé, vous devriez avoir la même ligne bleue que moi, saved succesfully !

On teste, l'exe fonctionne, un petit coup dans Peid pour voir si Aspack apparaît toujours? Eh non, on voit de nouveau la fausse signature faite par Goldocrack (1) !

L'Entry-Point a été remis à son origine en 1000h et la section où il se trouve est bien la .text (2) :

Merci de m'avoir suivi jusqu'au bout !

Coolmen / @2013 / http://xtx.a.nf/


Participants

---------------------------------------- Chapitre X - Participants ----------------------------------------

Par ordre alphabétique :

Coolmen
"I'm cool"

ga.
"_Sans maîtrise , la puissance n'est rien..."
(Pour être involontairemant à l'origine de tout ça ;))

Kiri
"Le bug se situe toujours entre la chaise et le clavier"
(Pour tes relectures et tes nombreuses corrections)

Kirjo
"Sois curieux de tout et apprends tous les jours."

Papa Bango

Todd

uLysse_31
"There is a crack, a crack in everything... That's how the light gets in."

Kirjo / @2012 / http://xtx.a.nf/


ARTeam Tutorial

http://cracking.accessroot.com

http://forum.accessroot.com
Assembleur Win32 Codage pour Crackeurs

Mots Clés | Coding(codage), ASM, Win32, GUI
Niveau | De débutant à Intermédiaire
Test OS | XP Pro SP2
Auteur Traduit par | Goppit - 26/04/2016 - goppit@hotmail.com SPOKE3FFF & DcSpirou - http://xtx.free.fr/ Site de GoldoCrack TEAM ODC

Outils utilisés:
MASM32 v9.0 | http://www.masm32.com/masmdl.htm
lib2inc v1.11 | inclus
WinAsm Studio Full v5.1.1.0 | http://www.winasm.net/index.php?ind=downloads&op =download_file&ide=119&file=WinAsm511.zip
WinAsm v5.1.3.0 Update* | http://www.winasm.net/forum/index.php?act=Attach&type=post&id=4767
WinAsm help file* | http://www.winasm.net/forum/index.php?act=Attach&type=post&id=381
Win32.hlp file | http://www.winasm.net/index.php?ind=downloads&op =download_file&ide=23&file=win32help.zip
OllyDbg v1.10 | http://www.ollydbg.de/odbg110.zip
RGN Generator v1.01 | http://www.codeguru.com/code/legacy/tools/RGNCreator_demo.zip
Table Extractor v1.34 | inclus
uFMOD | inclus
Extras:
M$ Platform SDK | http://www.Microsoft.com/downloads/details.aspx?FamilyId=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en
H2 Viewer v1.3.5.0 | http://helpware.net/downloads/H2Viewer135.zip
WinAsm MSDN h2help add-in* | http://www.winasm.net/forum/index.php?act=Attach&type=post&id=4096
Winspector v1.0b build 35 | http://www.windows-spy.com/files/Winspector_setupU.exe
SignFinder by Asterix | inclus

Notez S’il vous plaît que les liens donnés ci-dessus vous donneront accès à différents outils qui au moment où je rédige ce tutorial les liens sont encore bons , mais plusieurs d'entre eux sont très souvent mis à jour. Donc soyez prêt à chercher sur Google si certains liens sont HS. Le symbole ( * ) Indique que les mises à jour du forum WinAsm exigent que vous soyez enregistré. Je vous ai inclus un fichier qui regroupe les utilitaires Ici sur lesquels je ne trouvais plus d'activité sur leur site respectif.

Introduction

But de ce Tutorial

La plupart (voir même la totalité!) des meilleurs hackeurs, crackeurs et reverseurs sont également des programmeurs. Il est pratiquement impossible de bien suivre et apprendre le RCE, sans un minimum de connaissance en programmation ASM et un grand nombre des plus vieux fichiers txt sur l'apprentissage du Cracking - disaient je cite : "Avant d'aller plus loin apprenez l'Assembleur, et puis revenez lire ceci ". C'est encore plus vrai aujourd'hui que jamais car des exécutables utilisent des packeurs pour être protégé de l'utilisateur et sont de plus en plus complexes avec diverses méthodes d'approche sur l'unpacking, le déboguage et la reconstruction d'IAT. Cela a conduit des groupes de cracking à coder des outils personnalisés juste en se penchant sur ce sujet avec leur débogueur, désassembleur et outils Comme ImpREC. Elle a également conduit à la venue des loaders de plus en plus populaires, c'est le résultat de l'excellent travail en Cracking avec loaders de la série ARTeams. Ils ne sont cependant pas si facile à comprendre pour les débutants sans aucune Connaissances en codage ASM.

J'ai écrit ce tutorial pour combler et le manque de connaissance du débutant et faire un cours sur l'avancée des loaders, traceurs,… de même,que sur applications console(DOS) et comment utiliser et comment fonctionnent ces outils , mon deuxième objectif était d'illustrer sur le fait qu'il peut être facile de créer des applications GUI(fenêtre) en assembleur (ASM).

C'est grâce à l'esprit de partage des renverseurs de toutes les communautés sur la programmation assembleur ,que j'ai pu réutilisés et adaptés leur code d'une grande variété de sources que vous n'oublierez pas de remercier avec une grande gratitude lorsque cela vous sera possible par les "Greetz" habituels que vous mettrez dans vos futures applications codées grâce à leur aide .

Outils Requis

Il y a 4 principaux outils nécessaires:

1. Un assembleur
2. Un IDE ou Environnement de développement intégré
3. Un débogueur
4. Une source d'information concernant la plate-forme (le système d'exploitation -- WinXP pro dans mon cas) et ses Application(s) Programming Interface (L'API Win32)

Le choix de l'assembleur et IDE est d'ordre personnel. Le pack MASM32 mis à disposition spécifiquement pour coder des applications graphiques pour Windows et de plus il y a beaucoup plus de soutien et de code source disponible sur Internet qui utilisent la syntaxe de MASM . Je m'en rends compte fréquemment en recevant des critiques sur les lacunes et la lenteur en rapport à TASM ,et je pense que le code TASM est moins lisible, c'est pour ses raisons ci-dessus, je reste à MASM.

Je suis favorable à l'interface WinAsm Studio IDE parce qu'elle offre d'excellentes fonctionnalités et est très compacte et conçue uniquement pour MASM32 .Et ne présente aucun désagrément résultant de la nécessité de l'appui d'autres assembleurs. La structure des répertoires créés par son Assistant de nouveau projet est également beaucoup plus simple que pour RadASM par exemple. L'annexe 1 (Appendix 1) contient l'installation et les réglages détaillés de MASM32 et WinAsm plus quelques façons de les personnaliser.

Bien que le choix du débogueur soit également personnelle . Je suggère à tous les débutants de commencer avec un débogueur comme Ollydbg Il est très puissant et facile à utiliser par rapport à SoftIce et possède autant de plugins disponibles utiles.

La dernière des exigences qui s'ajoutera à celles ci-dessus sera remplie,soit en possèdent l'ancien fichier d'aide Win32.hlp ou par la mise à jour MSDN des bibliothèques disponibles en ligne dans le cadre de la Plate-forme SDK disponible en téléchargement libre chez M$.

Toutes les Informations que vous pourrez tirer profit dans ce tutorial va se faire en deux catégories principales:

1. MASM32 syntaxe -Comment écrire correctement les instructions en ASM sous MASM
2. L'architecture Windows et ses API(s) - Toutes les bonnes choses pour le reverseur

Nouveaux concepts de ces groupes sont Mis en évidence à la tête de chaque chapitre. Tous les codes Utilisés dans ces didacticiels sont écrit au format texte dans la La section des Sourcecodes. Ce fichier est là pour vous faciliter l'utilisation de WinAsm ou de les coller dans l'éditeur de votre choix. Des captures d'écran de l'éditeur de code WinAsm sont utilisés tout au long des didacticiels d'où le fait de la coloration syntaxique de manière à ce que ce soit plus lisible. Divers autres éléments utilisés dans les projets (Bitmaps, les icônes et les services publics) sont Également inclus dans un fichier zip fourni Ici avec ce tutorial. Une petite remarque finale à propos du code, je suis favorable au codage sous WinXP donc je n'ai fait aucun effort pour assurer la compatibilité avec Win9x. Certaines des routines (par exemple celle de ("alphablending") dans le chapitre keygen) ne fonctionnera pas sur les anciennes versions de windows.

B Compiler un Exécutable

Avant je vais expliquer brièvement Le processus de construction d'un exécutable. L'assembleur comprend en fait un ensemble de plusieurs consoles basées sur des composantes, notamment l'assembleur lui-même, un linker, un compilateur de ressources et autres. En principe vous obtiendrez de l'aide de l' IDE qu'il alimentera automatiquement la correction des syntaxes sur les instructions de commande (qui peuvent être complexes) .Ces éléments vous permettrons de voir les résultats d'un environnement graphique.

Les instructions du langage Assembleur sont écrites et sauvegardées en Simple fichier texte avec l'extension ". Asm". L'assembleur Traduit les instructions de ce code source en Code machine compréhensible par le CPU et convertit les valeurs numériques binaires en décimales. Le produit est un objet Fichier (. Obj), qui contient des instructions de la machine, des données et des informations nécessaires pour placer correctement les instructions dans la Mémoire lorsque l'application est exécutée.

Le Linker combine les fichiers objets et tout Les fichiers de ressources (voir ci-dessous) pour créer le fichier exécutable final. L'éditeur de liens cherche les fichiers objets à trouver toutes les fonctions externes et les liens utilisés dans les bibliothèques d'importation Pour ces fonctions. Le fichier résultant peut désormais être exécuté par Le système d'exploitation. Lorsque cela se produit, il est mappé en mémoire par le Loader de Windows, toutes les DLL dont il dépend sont aussi Chargées, et l'instruction de son entrypoint est appelée.

Ressources

Les ressources sont des données stockées dans l' Exécutable, y compris les icônes, stringtables, menus, Dialogues, des images, des données définis par l'utilisateur, etc Les ressources allouées à un programme sont Définies pour la première fois dans un script de ressources (. Fichier rc). Ceci est un simple fichier texte qui peut être écrit soit manuellement, soit en utilisant des ressources WinAsm's Visual Editor. Les ressources sont alors Compilées par un compilateur de ressources dans un fichier binaire de ressources (. Res) Qui est lié à l'exécutable par le linker. Les ressources sont accessibles par Numéros d'identification uniques qui sont définies dans le script de ressources. En ASM ces codes source de la même ID sont utilisés pour accéder à la ressource.


ARTeam Tutorial

 s

Squelette d'Assemblage de Base d'un Programme

Nouveaux Concepts:

Utilisation de WinAsm et
directives d'Assembleur MASM32
Utilisation d'Invoke avec fonctions d'API Win32

Nouvelles Fonctions d'API:

ExitProcess

Utilisation de WinAsm

La copie d'écran suivante illustre les parties de l'interface WinAsm. Le code source que vous tapez apparaît dans l'Éditeur de fenêtre (à gauche). La fenêtre de l'explorateur (à droite) montre les fichiers qui font partie de l'actuel projet, y compris le code asm, inclure des fichiers, Ressources,… Le boutton "Go All" (indiqué par le pointeur de la souris dans la capture d'écran) va assembler et lier votre code source et puis exécuter le fichier résultant. Les commandes passées à l'assembleur et de linker avec les résultats sont affichés dans la fenêtre de sortie (Inférieur).

Les 7 lignes de code montrées ci-dessus représentent le programme le plus petit qui assemblera en réalité, liera et exécutera. Il consiste en une simple instruction RET à l'entrypoint qui quand exécuté rend immédiatement le contrôle en retour Windows. Pour le faire dans un squelette utile autour du quel on peut construire des programmes réels nous remplaçons le RET d'un appel par ExitProcess - l'API standard pour quitter un appendice.

Pour commencer à coder, ouvrez WinAsm et cliquez sur le bouton à l'extrême gauche sur la barre d'outils. Notez que je fais mettre hors de service les chefs de projet récents au démarrage et j'utilise le Nouvel assistant de Projet Ajoutent - dans lequel il y a une grande amélioration sur la fonctionnalité existante - voir Ici. Si vous réglez votre système comme ici vous devriez voir:

Cliquez Ensuite et Finissez ensuite . La Norme(le Standard) EXE le type de projet est mise en évidence par défaut qui met l'assembleur et les options linker pour produire un Win32 GUI exécutable. WinAsm vous incitera à sauver le nouveau fichier de projet et le nouveau fichier d'ASM, après vous verrez que le rédacteur de code vide l'écran.

Collez maintenant skeleton.asm de la section de Code source de ce fichier dans le rédacteur de code la fenêtre. Le déclic "GO ALL" vous incitera d'abord à sauver le projet et le fichier asm à un emplacement approprié avant la construction et l'exécution de votre fichier. Votre code devrait y ressembler (l'annexe 1 de contrôle) : appendix 1 s'il y a des erreurs ou de ponctuation et de syntaxe seront colorée :

Nous analyserons maintenant ligne par la ligne. La plupart de ce code représente des directives (des instructions à l'assembleur). Il y a seulement une ligne de code de programme réel - l'appel à la fonction d'API ExitProcess.

.386 - Dit à MASM d'utiliser le jeu d'instruction d'Intel 80386.

.model flat, stdcall - Spécifie le modèle de mémoire plat et l'appel standard appelant la convention.

option casemap:none - Spécifie que les labels doivent être sensibles dans ce cas.

include kernel32.inc - Dit à MASM de traiter le fichier nommé (comme si le contenu a été copié dans le code source ici)

includelib kernel32.lib - Dit au linker qui importe des bibliothèques pour se lier avec (kernel32.lib dans ce cas).

.data - Spécifie des données initialisées (les variables que l'on donne à une valeur de départ) va ici.

.data? - Spécifie des données non initialisées ici (les variables qui n'ont pas encore de valeur). L'Espace est alloué quand le programme est chargé dans la mémoire mais la taille de fichier sur le disque n'est pas augmentée.

.const - Spécifie que l'on déclare une constantes ici.

.code - Spécifie que le code exécutable va ici.

start: - L'étiquette arbitraire pour spécifier le début de code (label de note est suivie par ":" quand il est d'abord déclaré).

invoke ExitProcess,0 - L'exécution commence ici, à l'instruction immédiatement au-dessous du label indiquée selon la directive de fin. Il y a seulement une instruction ici et il appelle la fonction d'API ExitProcess qui est la méthode préférée de finir un programme et rendre la main à Windows.

end start - Marque la fin du module et met le programme entrypoint au label indiqué (le label de note n'a pas besoin ":" ici comme on l'a déclaré plus tôt).

Les API Win32

L'API de Windows (Application, Programmant l'Interface) inclut une collection énorme de types de données, constantes, des fonctions et structure utilisé pour créer des applications pour OS Windows. La plupart des API fonctionnent, y compris ExitProcess que nous avons utilisé et dit, sont stockés dans 3 DLLS principales :

KERNEL32.DLL - Niveau bas kernel services
GDI32.DLL - Graphics Device Interface: drawing and printing
USER32.DLL - User Interface controls, windows and messaging services

Si vous utilisez des fonctions d'API de Windows dans votre programme, votre programme doit "importer" les fonctions du DLLS. Les bibliothèques d'importation (.lib des fichiers) contiennent des informations le linker doit résoudre des appels aux fonctions résidant dans les DLLS, donc le système peut charger la DLL indiqué et placer les fonctions exportées nécessaire quand votre code est exécuté. Par exemple, pour appeler la fonction d'ExitProcess qui réside dans kernel32.dll, vous devez lier votre code avec.

Include (.inc) Les fichiers contiennent "les prototypes" qui définissent les attributs de toutes les fonctions stockées dans la DLL du même nom. Celles-ci peuvent automatiquement produire des fichiers de bibliothèque utilisant le lib2inc utiliy. Il n'y a aucune façon facile de produire un fichier de bibliothèque d'un donné DLL, mais heureusement la plupart des fonctions d'API que vous exigerez sont placé(localisé) dans DLLS pour lequel la distribution MASM32 a des fichiers de bibliothèque appropriés.

La documentation d'API officielle Win32 est écrite pour C et C ++ des programmeurs et une fonction d'API typique est définie dans le format suivant :

Retour Type FonctionName ( ParamType1 ParamName1, ParamType2 ParamName2,...);

Utilisation de la fonction SetWindowText comme un exemple de la Référence du Programmeur Win32 :

La syntaxe C est facilement convertie en assembleur. Le mot BOOL est le type de données que la fonction son résultat et rendre comme on s'attend peut être méconnu. HWND est répété une fois représenter la taille et le type de données fournies à la fonction (vraiment juste un DWORD ici - ces types de données de Windows sont tous définis dans le fichier de windows.inc dans C:\masm32\include) et la seconde comme le nom des données (ici étant courts pour le module de la fenêtre). C'est vraiment

PUSH lpString
PUSH hWnd
CALL SetWindowText

Notez que dans l'assembleur les paramètres sont Pushés sur la pile dans l'ordre inverse comparée à la définition C de Win32.hlp, de droite à gauche. C'est le STDCALL la convention appelante qui est utilisée presque exclusivement dans des Fenêtres ASM.

Maintenant vous savez comment utiliser des définitions d'API de Win32.hlp, vous pouvez le consulter pour une explication de n'importe quelle API ça fonctionne nous l'utiliserons pendant ce tutorial..

Utilisation d'Invoke

L'inclusion de prototypes dans votre code source a 2 avantages dans MASM. Premièrement l'instruction INVOKE peut être utilisée au lieu du traditionnel PUSH/CALL . INVOKE est plus compact et les paramètres ne doivent pas être disposés dans l'ordre inverse :

INVOKE SetWindowText,hWnd,lpString

Le deuxième avantage d'INVOKE est qu'il permet la vérification de type donc le linker peut garnir de flag une erreur si on passe des paramètres incorrects à la fonction dans votre code. Des prototypes de fonction individuels peuvent être ajoutés au code manuellement au lieu de l'inclusion de .inc entier classent, mais doit arriver plus tôt dans le code que l'instruction INVOKE.

L'utilisation de prototypes et INVOKE peut être appliqué aux fonctions que vous écrirez vous-même comme nous le verrons par la suite.


ARTeam Tutorial

Programme Simple pour une boîte de message "MessageBox"

Nouveaux Concepts:

Variables et constantes
Utilisation d'Ollydbg pour désassembler et debogger votre code

Nouvelle fonction API:

MessageBox

Traditionnellement, les tutorials pour la programmation commence Avec une application qui affiche le message "Hello World "dans une console. Les fenêtres d'interface graphique équivalent à ça est la fonction MessageBox.Dans WinAsm pour commencer, ouvrez un nouveau Projet exe et coller dans le code messagebox.asm:

Cliquez sur "Go All", save project et ASM Fichiers, puis vous devriez voir ceci:

Maintenant nous allons analyser les nouvelles parties.

Header & Library Files

windows.inc Est un fichier maître qui définit inclure toutes les données Windows Types, les appels de fonctions, de structures de données et constante Identifiants pour vous permettre de les citer par leur nom dans votre code. A noter qu'ici, c'est windows.inc définit NULL et si MB_OK Que celles-ci peuvent être utilisés par leur nom pour rendre le code plus Lisible.

user32.inc et user32.lib sont requies pour invoker MessageBox.

Variables

Nous avons définis 2 Zéro fin de texte string qui appel MsgBoxCaption et MsgBoxText. Ceux-ci seront Affichée par la message box.

Variables et constantes sont tous déclarés De la même manière à partir de l'identifiant (name), la taille et aussi la valeur. Comme les chaînes varient en taille, ils sont définis comme un Octet zéro. Voici quelques exemples:

Program Code

La nouvelles instruction invoke la fonction MessageBox Qui prend 4 paramètres comme nous le verrons dans Win32.hlp:

Notre code passe ces paramètres comme Suit:

NULL - Il n'ya pas de fenêtre parente.
addr MsgBoxText - L'adresse de notre texte string.
addr MsgBoxCaption - L'adresse de notre caption.
MB_OK - Partie d'un ensemble de styles prédéfinis.

L' addr operateur Est utilisé pour transmettre des L'adresse d'un label d'identification ou à une fonction. Voir Ici pour la syntaxe.

NOTE EN OUTRE

Les messages eux-même peuvent être répartis dans Plusieurs lignes en utilisant le retour de char (13h) et d'une ligne vierge (10h) les codes à insérer une nouvelle ligne. Le code suivant:

Produira ceci:

Le dernier paramètre passé aux contrôles MessageBox en matière de style (entre autres choses), il a les boutons:

Et si elle affiche un icon:

Et le bouton qui est sélectionné par Default:

Les constantes qui les contrôlent sont Windows.inc défini dans le fichier qui peut aussi être ouvert dans WinAsm Comme le montre cette image:

Une sélection de ces derniers sont également définis dans C:\Program Files\WinAsm Studio\API\MasmApiConst.vaa Qui Les rend disponibles à partir de WinAsm auto complete menu popup. Utilisez les touches fléchées pour sélectionner et entrer à partir de la liste déroulante. Plusieurs constantes peuvent être combinés en un paramètre pour une Fonction en les séparant avec "or" or "+". Par exemple Modifier le code en:

invoke MessageBox, 0, addr MsgBoxText, addr MsgBoxCaption, MB_YESNO + MB_ICONASTERISK + MB_DEFBUTTON2

will produce this:

Nous pouvons voir à windows.inc que certains des Identificateurs sont des doublons et aussi quelles caractéristiques Messagebox par défaut utilisé par le système est composé de (Ce qui égale zéro). Paramètres et constantes pour les autres fonctions API sont appliquées de la même manière.

Si nous ouvrir nos messagebox programme (la Version originale) dans OllyDbg nous voyons un désassemblage du Code qui a été placé dans notre exécutable:

C'est une bonne habitude à prendre pour s'aider Découvrez le débogage des compétences et de voir en réalité ce que produit comme code MASM . L'invoque des instructions qui sont utilisés par l' Assembleur existent purement et pour la commodité du Programmeur ont été remplacés par la Séquence traditionnel PUSH / CALL . Les lignes en jaune représentent un saut thunk table Contenant des pointeurs vers les adresses d'importation pour le Tableau 2 Appels API que nous avons pris et qui sont ajoutés par le linker.

Olly permet également de charger et d'afficher notre code source le synchronisé avec le désassemblage. Pour les besoins de masm Et produire une version debug de nos app. Cela peut être Obtenus avec WinAsm. Par le "Make" menu select "Set Active Build" et choisi "Debug Version". Maintenant écrit"Go All" À nouveau et vous verrez certains des fichiers supplémentaires dans le cadre du dossier projet avec. Acabit et. Pdb extensions.

Cette fois, si nous chargeons nos exécutable dans Olly, Nous aurons un peu plus d'informations dans la Fenêtre désassemblage (tels que les noms de nos variables), mais aussi si nous Cliquons sur le lien "commentaire" en-tête de la barre des taches (voir curseur de la souris Dans la capture d'écran ci-dessous), il affiche le code source Correspondant aux instructions de désassemblage dans la colonne.

En outre, si nous sélectionnons"Source files" par le "View" menu dans Olly et doublecliquons sur notre fichier ASM puis L'ensemble du code source s'affichera dans une nouvelle fenêtre. Cela peut Être faits pour rester à la page via le menu droit et montrera L'instruction en cours d'exécution mis en surbrillance par une barre noire:

Cela peut rendre vos propres applications de débogage rapide Et plus facile mais rappelez-vous que le reverse est basé Sur la modification des exécutables sans le code source et en Général, vous serez très heureux si vous lancez une application de débogage Et qu'il s'agit d'une version debug plutôt que d'une version libre .


ARTeam Tutorial

Aperçu de l'architecture des fenêtres

Nouvelle Conception:

Types de fenêtres
Messages de la fenêtre
Procedures Windows
Windows Class
Le Message Queue & Message Loop
Boites de Dialogues & Controles fenêtre enfant
Handles

Cette section est nécessaire avant de passer aux autres, afin de comprendre la conception des fenêtres et des boites de dialogues.

T ypes de fenêtres

En programmation les fenêtres sont désignées comme des objets.Il existe diverses types de fenêtre telle que la fenêtre principale de demande qui peut contenir une barre de titre qui affiche le nom du programme, Un menu, et parfois une barre d'outils et barre de défilement. D'autres types De fenêtres sont les DialogBox, qui peuvent ou non avoir une Barre de titre,les MessageBox sont très basique.Boutons poussoirs, radio buttons, check boxes, list boxes, scroll bars, progress bars, et entrée de texte dans des champs prévue Font partie d' une fenêtre principale sont aussi appelées types de fenêtres "fenêtres enfants." Nous allons parler des boîtes de dialogue et des contrôles de fenêtres enfants de manière plus approfondie ci-dessous.

Il y a 2 areas(endroits) importants pour une fenêtre standard . Le client area est la partie principale de la fenêtre Où se produit l'affichage de l'application , tels que du texte ou Graphiques.La barre titre,barre menu,menu de la fenêtre,boutons minimise et agrandir,bordures, et barres de défilements Sont appelées collectivement pour la fenêtre nonclient area. Le système gère la plupart des aspects de l'endroit nonclient et de gérer l'apparence et le comportement de ses client area:

Fenêtre Messages

La fenêtre reçois la saisie de l'utilisateur, tels que l'interaction du Clavier ou de la souris sous forme de "fenêtres Messages "à partir du système d'exploitation.D'autres fenêtres utilisent aussi des Messages qui servent à communiquer avec d'autres fenêtres. Par exemple, Lorsque l' utilisateur déplace la bordure de la fenêtre d'une application pour la redimensionner, l' OS gère tout le code pour permettre à la taille de la fenêtre d'être changée, mais le soft va réagir en modifiant son Affichage pour remplir la nouvelle fenêtre. Comment savoir quand la fenêtre d'une app a été redimensionnée? L'OS envoie pour le programme une fenêtre avec un message du changement.Voilà un autre exemple comment savoir si le clic d'un bouton poussoir d'une fenêtre est géré par cette fenêtre. Dans la plupart des Programmes Windows, la majeure partie du programme est consacrée à La manipulation des messages. Un message est constitué d'un MessageID commençant par Les lettres indiquant le type du message (par exemple WM_) et Deux paramètres (lParam et wParam) qui (si nécessaire) Contiennent des informations supplémentaires.

Procedure Windows

L'OS renvoi des messages à un programme de Appelant une fonction spéciale (La "procédure Windows" ou WndProcCela permet de conserver l'intérieur et de le transmettre le message en tant que paramètre.Les Fonctions écrites par le programmeur qui appelle l'OS de cette façon sont appelés "procedures callback". Chaque fenêtre que créé un programme est associée à une Procédure Windows qui traite les messages de l'OS et d'autres fenêtres. Toute l'action réelle se produit par la procédure Windows -- Presque tous les programmes Windows gèrent Un message par procédure Windows. La procédure Windows détermine ce qu'affiche la fenêtre dans son espace client et Comment elle réagit à la saisie de texte par l'utilisateur.Cette fenêtre s'appelle WndProc Quand une fenêtre est créée et détruite. Votre Procédure Windows permet de recevoir des messages Dont il n'a pas besoin de le traiter il le fait lui-même et dans ce cas vous Devez avoir un message par défaut de Windows handler DefWindowProc pour la transformation.

Windows Class

Une création de fenêtre est toujours basée sur une"Windows Class".La Windows Class Définit la Procédure qui traite les messages de la fenêtre. L'utilisation d' une Windows Class permet à plusieurs fenêtres d'être créé en fonction de la Même classe, et donc d'utiliser la même procédure Windows. Par exemple, toutes les fenêtres, y compris les boutons poussoirs, Des cases à cocher, boutons radio, sont créées basés sur la même Windows Class Cette Windows Class est associée à une Procédure Windows située dans une DLL système qui traite les messages de sur tous les boutons Windows. Avant de créer une application Windows, vous devez enregistrer une Windows Class en appelant la fonction RegisterClass. La Windows Class définit également les grandes Caractéristiques d'une fenêtre comprenant des icônes et couleurs en arrière-plan .

Lorsque vous créez une fenêtre en appelant la fonction CreateWindow , vous spécifiez plus en détails L'information sur l'affichage de la fenêtre. Par exemple Nous savons que tous les boutons fonctionnent de la même façon car ils utilisent tous WndProc , mais certains boutons n'ont pas le même aspect. Ils peuvent avoir différentes tailles, différents endroits sur l' Écran, et différentes chaînes de texte. Ces caractéristiques Font partie de la définition Windows plutôt que de la Class Windows.

Pour les contrôles fenêtre enfant mentionnés ci-dessus 6 classes sont prédéfinies: BUTTON, COMBOBOX, EDIT, LISTBOX, SCROLLBAR, et STATIC. Ces "contrôles sont" Enregistré en appelant la fonction InitCommonControls.

Le Message Queue et Message Loop

Quand un programme Windows Commence l'exécution L'OS crée un "message queue" pour le programme. Ce Message Queue ou sont stockés les messages de toutes les fenêtres q'un programme pourrait créer.Une application Windows inclut une courte partie Appelée "message loop" Qui récupère les messages De la Message Queue en appelant la fonction et GetMessage Les expédie à l'intérieur en appelant par DispatchMessage la fenêtre . Les autres messages sont envoyés directement à La procédure Windows, sans être placé en Message Queue (file d'attente).

On dit que les Message Queue sont affichés dans Message Queue nonqueued et que les messages sont envoyés vers la procédure Windows.Message Queue en général est le résultat de la saisie de l'utilisateur sous forme de raccourcis clavier, les caractères en rapport à la frappes de touches, mouvements de souris et clics de souris, mais également qui inclu les messages à affichage colorés (WM_PAINT) et le message de sortie (WM_QUIT). Nonqueued messages sont tout le reste et sont souvent le résultat d'appeler les fonctions Windows.Par exemple, lorsque les appels WinMain fenêtres crée la fenêtre et dans le processus envoie la fenêtre WM_CREATE intérieur un message.

Dialog boxes et Child Window Controls

Les Boîtes de dialogue ne sont rien de plus que la normale, qui sont des fenêtres Conçues pour fonctionner avec les contrôles de fenêtre enfant. Elles peuvent être Utilisées comme un périphérique d'entrée à la fenêtre principale de votre app Ou comme étant la fenêtre principale . La taille et la disposition d'une boîte de dialogue et ses contrôles sont définies dans un modèle de boîte de dialogue des ressources Script. Cela peut être écrit manuellement mais c'est bien mieux de le faire À l'aide de Visual WinAsm's Resource Editor.

Quand un programme invoque une boîte de dialogue basée Sur un modèle, Windows est responsable de la création des Boîte de dialogue de fenêtre popup et les contrôles de fenêtre enfant , et fournissant une fenêtre pour la procédure d'instruction des messages de la boîte de dialogue , y compris toutes les entrées clavier et souris. Depuis le contrôle de la fenêtre enfant et de leur propre processus de la souris et du clavier Les messages et les asocies à la fenêtre parente lors de leurs Modifications, cele allège énormément le travail des programmeurs . Le code de Windows pour tout cela est Parfois appelé le «gestionnaire de la boîte de dialogue."

Beaucoup de messages traités par le Boîte de dialogue de gestionnaire sont aussi passés à une procédure de rappel Au sein de votre propre programme appelé une boîte de dialogue ou la procédure de dialogue procedure.A procédure est similaire à celle d'un Procédure dans la fenêtre Windows qui envoie des messages à la Procédure quand il a à donner l'information ou les tâches à accomplir . La plupart des procédures de la boîte de dialogue du processus WM_INITDIALOG WM_COMMAND message et ses messages envoyés par les contrôles.

Contrairement à une Windows Procédure, une boîte de dialogue de la procédure n'appel jamais la fonction DefWindowProc. Au contraire, elle renvoie la valeur TRUE Si elle traite un message ou FALSE s'il ne le fait pas. En d'autres termes, si une procédure de dialogue ne traite pas un message, il doit retourner FALSE directement à Windows qui traite ensuite les messages Interne. La seule exception à cette règle est WM_INITDIALOG message. La procédure de la boîte de dialogue doit retourner TRUE pour Windows directement à traiter bien avant la WM_INITDIALOG Message.

Vous pouvez faire communiquer un contrôle fenêtre enfant et une Boîte de dialogue (par l'intermédiaire de son unique Numéro de control ID) en utilisant la fonction SendDlgItemMessage.Win32.hlp Disponibles sont dans la liste des messages de Windows, mais fournit aussi - Le contrôle de plusieurs fonctions de l'API spécifique pour obtenir et définir des données Rapidement, par exemple GetDlgItemText, etc CheckDlgButton Ces fonctions de contrôle spécifiques sont prévues pour la commodité du programmeur et de rendre le code source plus Lisible. Recourir à SendDlgItemMessage seulement si il n'y a aucun Contrôle des appels d'API .

Les 2 principaux types de boîte de dialogue

model - Fréquente, eg "About" box dans le menu d'aide. L'utilisateur ne peut pas Basculer entre les Boîtes de dialogue et une autre fenêtre de votre programme. L'utilisateur doit Répondre à la boîte de dialogue d'abord. L'utilisateur peut, toutefois, Passer à un autre programme. Créée en appelant DialogBoxParam Qui retourne une Valeur seulement après que la boîte de dialogue soit détruite. Si vous incluez DS_SYSMODAL le style de modèle dans la boîte de dialogue , il se Produira un model de Système de dialogue qui doit être terminé Avant que l'utilisateur ne puisse faire quoi que ce soit d'autre dans Windows.

modeless - eg text "find" Dans la case Traitement de texte. Permettent à l'utilisateur de basculer entre la boîte de dialogue Case et la fenêtre qui l'a créée ainsi que tout autre Programme. Créée en appelant CreateDialogParam qui retourne immédiatement avec la fenêtre de dialogue .

Il ya plusieurs autres Différences importantdes :

1. Boîtes de dialogue modale en général d'une Légende de la barre de menu et d'un système de boîte de permettre à l'utilisateur de se déplacer La boîte de dialogue dans une autre zone de l'écran en utilisant soit La souris ou le clavier. Vous n'avez pas besoin de cela avec un modale Boîte de dialogue car l'utilisateur ne peut rien faire dans la Fenêtre sous-jacente de toute façon.

2. Si vous ne lui faites pas comprendre par WS_VISIBLE , ni appel ShowWindow, la boîte de dialogue modale ne sera pas Affiché du tout.

3. Contrairement aux messages des boîtes de dialogue modales Et les boîtes de message, les messages des boîtes de dialogue modale à venir Par le biais de votre programme le message de la file d'attente qui doit être modifié Faire passer ces messages à la fenêtre à l'intérieur de la boîte de dialogue.

Boîtes de dialogue communes

Windows prépare et prédéfinie "commun" pour les boîtes de dialogue L'utilisation par vos applications, y compris les fichiers, d'impression, de couleur, Police, la recherche et les boîtes de dialogue. Ils sont là pour Fournir une interface utilisateur standardisée et résider dans Comdlg32.dll afin de les utiliser, vous devez lier à Comdlg32.lib. Vous créez ces boîtes de dialogue en appelant Fonctions appropriées dans la bibliothèque commune de dialogue. Pour ouvrir la boîte de dialogue Fichier, c'est GetOpenFileName, pour "sauvegarder dans" dialog c'est GetSaveFileName, pour l' "impression" dialog c'est PrintDlg Et ainsi de suite. Chacune de ces fonctions prend un pointeur vers une structure Comme son paramètre. Ils sont énumérés dans Win32.hlp.

WinMain, Handles et Instances

L'EntryPoint d'un programme Windows est la fonction WinMain, Qui est appelée par le système d'exploitation afin de Commencer l'exécution du programme a été chargé à partir du disque En mémoire par le chargeur Windows. En C / C + + des programmes, le Label "WinMain" doit être utilisé en assembleur, mais un label peut Être utilisés. Quand un exécutable est mappé en mémoire par le Fenêtres chargeur, la version en mémoire que l'on appelle un module. Windows 32 bits dans l'adresse de départ en mémoire (linéaire Adresse) où le fichier de cartographie Commence équivaut Le module manipuler.

A handle est simplement un 32bit (DWORD-sized) Nombre qui utilise le système d'exploitation D'identifier de façon unique chaque objet qu'il crée par exemple un Fenêtre ou le bouton-poussoir. Vous obtenez la poignée de Windows et Puis utilisez le levier à d'autres fonctions. Le module identifie gérer le programme. Le module est également manipuler Connu sous le nom de «instance handle" qui est le premier paramètre WinMain prises par la fonction et est tenu d'en faire un argument Windows à d'autres fonctions.

Dans les premières versions de Windows, lorsque vous avez exécuté Plusieurs instances du même programme, toutes les instances de l' L'application du code partagé et à lecture seule. Un programme Pourrait déterminer si les derniers cas de se dirigeaient Et puis sauter certaines corvées et déplacer des données de la précédente Exemple dans sa propre zone de données. Dans les versions 32 bits de Windows, ce concept a été abandonné. Le "précédent Exemple de faire "est resté le deuxième paramètre de WinMain mais est maintenant toujours NULL Ou 0.

Easy GUI applications utilisant les boîtes de dialogue

Boîtes de dialogue peuvent être utilisés comme données d'entrée Dispositif de la fenêtre principale de votre application ou de la fenêtre principale Elle-même. Ceci est réalisé en appelant à créer DialogBoxParam Une boîte de dialogue modale sans fenêtre parente. Cette approche Simplifie considérablement la conception du programme en utilisant une boîte de dialogue Procédure au lieu d'une fenêtre de la procédure et rend un message loop inutile puisque les messages sont envoyés directement à l' intérieurde la boite de Dialogue. La boîte de dialogue peut être créé dans un script Ressource dans un éditeur de visuels en utilisant les Contrôles fenêtre enfant qui effectuent la plupart de leur propre traitement du message. Vous N'avez même pas besoin d'enregistrer une classe Windows. Nous utiliserons cette Approche pour les exemples de ce tutorial.


ARTeam Tutorial

Programme Simple pour une boîte de dialogue "DialogBox"

Nouvelles Conceptions:

Création d'une dialogue box dans une application
Utilisation Child Windows Contrôles - boutons
Création d'un script ressource
Fonction prototypes
Utilisation Windows messages

Nouvelles fonctions API :

GetModuleHandle
DialogBoxParam
SetDlgItemText
SendMessage
EndDialog

Dans cette section, nous allons créer Un programme avec une simple boîte de dialogue avec un bouton Affiche un message et un bouton de sortie. Début WinAsm et d'ouvrir une nouvelle Exe projet. Nous allons d'abord créer la boîte de dialogue de modèle WinAsm utilisant Visual Ressource Editor. Ceci affiche les contrôles sur une grille L'intérieur de votre boîte de dialogue telle qu'elle apparaîtra dans votre programme. La Boîte à outils de dialogue et barres d'outils (à partir du menu Affichage) permettent D'ajouter plus de contrôles et disposez-les visuellement. Plutôt Que de taper sur une ressource script manuellement, toute la boîte de dialogue Peut être conçu de cette manière.

Soit Sélectionnez Projet>Add New Rc, ou clic-droit dans la barre de l'explorateur project et selectionnez Add New Rc. Visual est activé en mode par Défaut donc de cliquer sur "Add New Dialog" (Le 1er bouton dans la Toolbox bar) Et vous devriez voir ceci:

Maintenant, nous allons ajouter une Captions, 2 boutons et Une Editbox. Cliquez sur l'Onglet "Resources" en bas à droite de L'écran, double-cliquer la case et entrez Caption "Simple Dialog Box Program". Maintenant cliquez sur le bouton "Edit" (4èm bouton dans la Toolbox) Et vous verrez dans une boîte quelque chose comme ceci:

Maintenant dans la barre de l'explorateur, cliquez sur IDC_EDIT1002 cela l'efface et en dessous de son champ Texte. Finalement, cliquez sur le "Bouton" dans la barre de Toolbx et ajoutez 2 boutons, et changez le texte des boutons par "Say Hello" et "Exit"

Les boutons de la barre d'outils de dialogue vous permettent l' Alignement et la modification de la taille des contrôles pour les rendre plus agréables à L'œil. Pour voir le script de la ressource que WinAsm a créé pour nous, désactivez le mode visuel en pressant la touche F12, désélectionnez"Visual Mode" Ressources dans le menu ou en appuyant sur le Bouton de la barre d'outils :

La désactivation du mode visuel vous permettra également de créer un script ressource manuellement ou d'utiliser celle que j'ai Inclus dans le code source de la section et de la coller dans le script de dialogbox1.rc:

Afin de commencer à écrire le code pour Boîte de dialogue de notre modèle nous avons besoin de connaître les noms et ID de contrôle de la boîte de dialogue, la saisie et les boutons Comme indiqué dans les 4 premières lignes du script de la ressource ci-dessus.

Sélectionnez Untitled1.asm dans la barre de l'explorateur de projet et collez le dans dialogbox1.asm venant du sourcecode section:

Le petit signe "+" à gauche du bouton montre le début et la fin de la procédure de la dialogbox. Cliquant dessus cela vous montre l'ensemble de la procédure comme indiqué ci-dessous:

Finalement sauvez votre projet,script ressource et fichier ASM et pressez le bouton Go All. Vous devriez voir ceci lorsque vous Cliquerez sur le bouton "Say Hello".

En cliquant sur "Go All" Cela compile le Script de ressources, ainsi que l'assemblage et ses liaisons. Si vous Modifier la mise en page de la boîte de dialogue ou de ses contrôles, vous pouvez Recompiler le script sans ressource ré assembler et Faire un lien en sélectionnant>Compile RC. Maintenant, nous allons analyser Le code. L'en-tête et les dossiers dans la bibliothèque devrait Être explicites maintenant.

Prototypes

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD - En 1er on déclare la fonction prototype pour DlgProc Donc nous Pouvons nous référer à l' addr lorsque nous invoquons DialogBoxParam plus tard.

Variables

Notre Message string - est terminée par null "Hello World!".

hInstance - (est défini dans windows.inc DWORD) uninitialised mais aura le handle de notre module de la Boîte de dialogue pour l'appel à DialogBoxParam.

Code Programme

invoke GetModuleHandle - Récupère le module handle ou l'instance handle de notre programme. C'est l'ID de votre programme et est Utilisée comme un paramètre à plusieurs fonctions de l'API de notre programme Doit appeler, donc il est d'usage de le récupérer au début de notre programme.

mov hInstance,eax - Déplace le Résultat de l'API du GetModuleHandle Eax dans une variable pour cela, nous avons déclaré ci-dessus (au retour d'une fonction Win32, la fonction nous retourne la Valeur, le cas échéant, qui est placée dans eax).

invoke DialogBoxParam - Crée un modèle de boîte de dialogue et retourne dans la boîte de dialogue Est détruite. Prend 5 paramètres: l'exemple de traiter la demande, Boîte de dialogue de modèle, la fenêtre parente manipuler, l'adresse De la boîte de dialogue intérieur, de dialogue et de données spécifiques.

invoke ExitProcess - L'instruction finale comme décrit dans notre chapitre skeleton.

Dialog Procédure

La procédure de dialogue est une fonction de rappel de service qui traite des messages envoyés à la boîte de dialogue. Il a quatre paramètres et retourne TRUE ou FALSE selon si vraiment il traite le message. Dans ce cas les commandes de fenêtre enfant envoient des messages à la procédure de dialogue en l'appelant et passant le message .

La première ligne consiste en déclaration de fonction en DlgProc. Notez les paires de type de paramètres qui suivent la directive PROC. Les paramètres consistent en Handle de fenêtre, un identificateur de message (uMsg) et deux paramètres de message (wParam et lParam) qui spécifie des informations de message supplémentaires.

Quand les commandes de fenêtre enfant envoient un message, le paramètre uMsg contient WM_COMMAND et wParam contient le contrôle ID. Le traitement de message est accompli en utilisant une série IF-ELSEIF-ENDIF qui vérifie d'abord pour voir si uMsg contient un message WM_COMMAND d'une des commandes ou un message WM_CLOSE.

Si le message est WM_CLOSE alors EndDialog est appelé pour détruire la boîte de dialogue. EndDialog ne le fait pas immédiatement, il met seulement un flag pour le directeur de boîte de dialogue interne et continue à exécuter les instructions suivantes.

Si le message est WM_COMMAND alors une série IF-ELSEIF-ENDIF consécutive vérifie la valeur dans wParam pour voir comment répondre - dans ce cas si le bouton "Say Hello" (ID 1003) ou Le "Exit" bouton (ID 1004) à été pressé.

SetDlgItemText - Met le texte du contrôle (l'editbox ID 1002).

SendMessage - Envoie un message WM_CLOSE pour la procédure de dialogue.

La ligne suivante XOR eax,eax juste zeroes eax avant RET L'exécution de retours à DialogBoxParam et par la suite ExitProcess est appelée.

Analyse dans le Débuggeur

Comme d'habitude nous devrions ouvrir notre application dans Ollydbg pour voir comment le code a été assemblé :

Puisque nous codons en ASM, nous obtenons un désassemblage propre et agréable dans Ollydbg. Le premier crochet inclut à gauche les 4 premières lignes de notre code et la deuxième crochet inclut la procédure de dialogue qui est moins facile à comprendre au premier coup d'œil. MASM a converti IF-ELSEIF-ENDIF en haut niveau construit dans comparent (CMP) et instructions de saut conditionnel. Comme auparavant, le bloc final d'instructions mises en surbrillance en jaune n'est le saut thunks indiquant la Table d'Adresse d'Importation.

Notes

Le champ de Nom de la boîte de dialogue et ses commandes peuvent être changés en quelque chose de plus significatif par double-clic dans le rédacteur visuel. Le script sera changé en conséquence, eg :

On peut alors déclarer ces noms comme constantes dans le fichier ASM et utilisé dans le code au lieu du contrôle numérique IDs pour améliorer la lisibilité :

Il y a quelques particularités quant au paramètre du modèle de boîte de dialogue a passé à DialogBoxParam. Si le nom de boîte de dialogue et le contrôle ID sont définis dans le script de la ressource (Comme ils sont à la ligne 1 de l'exemple WinAsm a fait pour nous) Alors on peut passer le contrôle ID seul à DialogBoxParam. Si on déclare aussi le nom et le contrôle ID comme une constante dans le fichier ASM alors on peut passer le nom à DialogBoxParam. Si le nom et le contrôle ID est pas définis dans le script de la ressource alors on peut déclarer le nom comme une variable string dans la section .data du fichier asm et passé à DialogBoxParam avec l' addr opérateur.

Inclusion de la ligne include resource.h Au début du script de la ressource permet l'utilisation des noms descriptifs plutôt que les numéros des Styles qui définissent l'apparence du dialogue et de ses commandes.


ARTeam Tutorial

Boîte de dialogue avec Icône et Menu

Nouveaux Concepts:

Ajout d'une icône et menus à une boîte de dialogue
Utilisation de styles de fenêtres
L'utilisation d'un manifeste pour appliquer le Style Visuel XP aux commandes

Nouvelles fonctions API:

LoadIcon
GetDlgItem
SetFocus
GetDlgItemText

Nous nous pencherons sur le dernier exemple en ajoutant une icône, un menu et un certain code pour montrer le texte tapé dans la boîte d'édition dans une boîte de message. Nous commencerons aussi par le curseur dans l'editbox.

Créez d'abord nouveau projet WinAsm en ajoutant un nouveau fichier de rc. changez le mode visuel (F12) colle alors dans dialogbox1.rc du dernier projet. Tournez maintenant le mode visuel en arrière sur et cliquez sur l'étiquette de ressources dans la barre d'explorateur. Mettez d'abord en évidence la boîte de dialogue et le titre en double-cliquant pour le changer "la Boîte de dialogue avec le menu d'Icône.

Maintenant nous voulons ajouter un menu appelé "Menu" avec 4 articles; "Get Text", ""Clear Text"", un séparateur et "Exit":

1. Cliquez sur le deuxième bouton de la barre d'outils dans la barre d'explorateur (New Menu) et entrez "MyMenu" le nom de menu et pour le premier article de menu entrez "Menu" dans la boîte de Titre, efface le Nom et cliquez insert.

2. Pour l'article de menu suivant entre le Titre "prend le Texte", le Nom "IDM_GETTEXT", clic droit sur la flèche jaune Insérez ensuite:

Ajoutez maintenant encore 3 articles de menu pour le ClearTexte, un séparateur (tapez "un-" dans la boîte de Titre et videz la boîte de Nom) et la Sortie et dentelez-les avec les flèches jaunes. Cliquez finalement sur OK :

Ensuite nous dirons à la boîte de dialogue d'utiliser notre menu et changerons le style de dialogue :

1. Cliquez sur OK alors le clic des Dialogues dans la barre d'explorateur et mettez en évidence IDD_DLG1001. Clic dans le champ de menu et sélectionnez "MyMenu".

2. Clic dans le champ de Style et le double-clic DS_CENTER suivi par OK. Cela fera que notre boîte de dialogue apparaîtra dans le centre de l'écran plutôt qu'en haut à gauche. Beaucoup de ces paramètres sont utiles, eg tournant de WS_SIZEBOX empêche que notre boîte de dialogue soit redimensionnée qui la ferait paraître un peu étrange.

Ensuite nous ajouterons une icône (inclus Ici - Extrayez-le dans votre dossier de projet). Le clic sur le bouton pénultième dans la barre d'explorateur, le clic ajoute et navigue votre icône. Remplissez le d'un Nom et ID par double-clic dans les champs alors cliquez sur OK :/font>

fermez finalement le mode visuel pour voir le script. De nouvelles parties sont encadrées en rouges. Si n'importe lequel du nouveau contrôle IDs a des duplicata les changer maintenant - ils doivent tous être uniques (encadré en vert):

Sauvez votre nouveau script de la ressource comme dialogbox2.rc et maintenant nous verrons ce que le code a ajouté pour utiliser ces ressources. clic sur l'explorateur de projet, choisissez Untitled1.asm et la collez dans le code de dialogbox2.asm. Comme vous pouvez voir il n'a pas beaucoup changé sauf quelques nouvelles variables:

Et la procédure de dialogue traite des messages du menu:

Go All Devrait vous le donner:

Maintenant nous allons analyser le code.

Variables

De nouvelles variables incluent des strings de Titre pour notre messagebox, buffer - un tableau non initialisé de 512 octets pour recevoir le texte de l'editbox dans et le contrôle IDs des articles de menu et l'icône.

La Procédure de Dialogue

Le premier message que notre procédure de dialogue reçoit de Windows est WM_INITDIALOG. Si nous le traitons avec notre procédure de dialogue nous pouvons mettre n'importe quel code d'initialisation ici.

LoadIcon - Charge la ressource d'icône indiquée de l'exécutable et retourne son handle dans eax. Le message WM_SETICON charge la procédure de dialogue d'associer l'icône avec l'handle indiquée à la boîte de dialogue.

GetDlgItem - Trouve l'handle du contrôle indiqué (l'editbox ici) et le retourne dans eax.

SetFocus - Le force à saisir le centre au contrôle avec l'handle indiquée (de nouveau l'editbox ici donc l'utilisateur peut y taper le texte immédiatement).

Les lignes suivantes de code traitent des messages WM_COMMAND comme auparavant. En plus de l'arrivée de la fenêtre enfant contrôle ceux-ci sont maintenant produits si l'utilisateur a choisi un article de menu. Si il vous arrive d'utiliser les mêmes codes d'ID pour des menus et des commandes de fenêtre enfant, vous pouvez les différencier entre elles en examinant la valeur de lParam, qui sera 0 pour un article de menu:

 | Low word of wParam | High word of wParam | lParam
Menu | Menu ID | 0 | 0
Control | Control ID | Notification code | Handle fenêtre enfant

Si lParam est le zéro, le courant WM_COMMAND le message est d'un menu. Vous ne pouvez pas utiliser wParam pour le différencier entre un menu et un contrôle depuis le menu ID et contrôler ID peut être identique et le code d'avis peut être le zéro.

Dans notre cas nous avons IDS différent donc nous pouvons traiter tous les messages dans la même boucle sans devoir examiner de ceux avec un zéro lParam une seconde si la boucle. Nous revisiterons le concept de spliting les mots d'ordre hauts et bas des messages pour obtenir le code d'avis dans le chapitre custom keygen .

La seule nouvelle fonction que nous avons utilisée est GetDlgItemText:

Utilisation du Style Visuel d' XP

Vous pouvez remarquer bien que les grands traits des boutons dans le rédacteur de Ressource WinAsm aient les coins arrondi dans le style XP, une fois l'exécutable compilé on voit qu'il remet à la place le style boutons carré. Pour utiliser le nouveau Style Visuel XP avec les commandes communes vous devez utiliser un fichier de manifeste. C'est une norme(un standard) xml (la langue de majoritaire extensible) le fichier qui doit être édité seulement pour inclure le nom de votre application. Le nom du fichier de xml lui-même peut être quoi que ce soit.. Il devrait être placé dans le même dossier que les fichiers source et est spécifié par une ligne seule dans le script de la ressource.

Le manifeste peut être ajouté au projet en cliquant sur le label de la Ressources dans la barre d'explorateur du projet, cliquez Ressources Ajoute alors, et l'ajout du fichier de manifeste. Un nom de ressource n'est pas nécessaire mais l'ID pour le manifeste doit toujours être 1 ou il ne marchera pas:

Cela ajoute la ligne suivante au script de la ressource juste au-dessous de la ligne de d'icône (qui pourrait être fait manuellement):

1 24 DISCARDABLE "dialogbox2.xml"

Sauvez finalement dialogbox2.rc de nouveau et il devrait correspondre à la version dans la section de code source. Maintenant en recompilent vous devriez voir:

Notez les boutons sont arrondis avec le contour bleuâtre. Le fichier de manifeste est aussi inclus dans la section de codes source. Juste pour la référence le contenu consiste en ce comme suivants:


ARTeam Tutorial

Model d'un Keygen Simple

Nouveaux Concepts:

Utilisation du presse-papiers avec messages de fenêtres
Écriture de votre fonction propres
Sauts Conditionnels(Conditional jumps) et Labels
Boucle et Labels Anonymes

Nouvelles fonctions d'API:

SendDlgItemMessage
lstrlen

Dans ce tutorial nous ferons un keygen simple et ajouterons le code par exemple l'algorithm rippé de la génération de serial de n'importe quelle routine de protection. Nous ajouterons aussi le code pour copier le produit de la génération de serial au presse-papiers.

Gonflez à bloc WinAsm comme d'habitude, créez un nouveau projet norme(standard) exe et ajoutez un nouveau script de ressource. Au label de la Ressources du clic de barre d'explorateur de projet Ajoutez le Nouveau Dialogue et ensuite des Ressources ,ajoutez notre icône (comme dans l'exercice précédent).

Ajoutez 2 commandes d'édition pour entrer à notre nom et montrer notre serial, 2 commandes statiques pour labeler l'editboxes et 3 boutons comme indiqué :

Changez le Style du dialogbox pour inclure DS_CENTER, les 2 labels statiques pour inclure SS_CENTERIMAGE et editbox serial pour inclure ES_READONLY. Le script est inclus dans la section de code source comme simplekeygen.rc

Maintenant ajoutons le fichier simplekeygen.asm dans l'explorateur de projet et cherchez-le dans la section de code source. Voici le corps principal de l'application:

Suivi selon la Procédure de Dialogue:

Et la procédure génère le calcule du numéro du serial:

Maintenant nous allons analyser ce code-ci.

Variables

Il y a 2 variables de string non initialisées dans lesquels sont stocker la saisie de l'utilisateur pour (name) et le serial calculé.

La Procédure de Dialogue

Nous avons des routines pour traiter la saisie de l'utilisateur de nos 3 boutons.

La réponse avec le bouton Generate, Nous ne faisons simplement que prendre la saisie du Nom de l'utilisateur dans son editbox et le stockons dans le NameBuffer en tant que variable, appellée pour Produire la routine qui génère et calcul le serial dans l'editbox Serial.

Le bouton Copy fait en sorte que le serial dans l'editbox serial soit copié dans le presse-papiers. Cela est réalisé entièrement en envoyant des messages de fenêtres à editbox serial. C'est beaucoup plus facile que l'écriture d'instructions d'assemblage d'exécuter la même chose que le code est plutôt malpropre. Notez que de Win32.hlp le texte à être copié doit être choisi d'abord :

et le code pour choisir le contenu entier d'une editbox est :

Il est important de noter qu'il y a des fenêtres de messages pour accomplir un nombre énorme de tâches dans des fenêtres sans écrire un long code pour faire ce même travail.

Le bouton Exit fonctionne comme décrit précédemment.

La Procédure qui génère et labels

La fonction qui génère est un exemple d'une simple routine qui change complètement l'ordre des lettres dans la string entrée dans l'editbox du Nom. Notez que nous ne donnons pas de paramètres à la fonction et elle ne rend pas de valeur comme telle. Il n'y a donc aucun avantage d'écrire un prototype et l'appeler avec invoke.

La routine a été d'abord écrite sans ces lignes

test eax,eax
jle NOINPUT
NOINPUT:

Mais en cliquant sur Generate ça cause un crash si NameBuffer est vide (c'est-à-dire il n'y a aucune saisie de l'utilisateur). La première instruction lstrlen Calcule la longueur de la string dans NameBuffer et met le résultat dans eax. Le fichier Win32.hlp : "la fonction lstrlen place la longueur dans des octets (la version d'ANSI) ou des caractères (Unicode version) de la string indiquée (aucun caractère entré donc nul ) ".

Test eax,eax Met le flag à zero si eax est zero et jle est un saut conditionel (Jump if Less or Equal) => (Saute si inférieur ou égal) saute vers le label NOINPUT (Aucune entrée) et l' instruction ret si le flag est mis à zero. Autrement dit la fonction Generate retourne juste sans exécuter aucune de ses instructions internes au lieu de cracher(planter) s'il n'y a aucun caractères saisis par l'utilisateur.

Les labels sont des noms que vous pouvez donner dans une partie du code et l'assigner une instruction de saut conditionnelle . Quand un label est défini il est suivi par deux-points " : ", mais quand le label apparaît derrière l'instruction de saut on ne met pas les deux-points " : " .

Si la longueur de la string dans eax est zéro alors elle est copiée dans ecx par l' instruction mov . ECX est généralement utilisé comme un compteur pour spécifier combien de fois l'instruction qui forme la boucle doit être effectuée - dans ce cas ECX sert à traiter chaque caractère de la string et de boucler tant que tous les caractères de la string n'ont pas été énumérés par ECX .

Ensuite les adresses mémoire du NameBuffer et des variables SerialBuffer sont copiées dans ESI et EDI. Ces deux registres sont utilisés pour indexer la source et la destination pour des manipulations de string.

@@: Déclare un label anonyme. Dans de longues routines utilisant un tas de sauts ou de boucles il est encombrant de taper des noms différents pour tous les labels nécessaires donc MASM peut utiliser des labels anonymes pour des sauts courts ou des boucles. Utiliser ce label dans le code @f jumps forward et le suivant @@: et @b jumps backward Au plus proche @@:

Finalement la string changeant complètement la routine marche comme cela :

D'abord le compteur ecx est dec eleve à 1 pour que la boucle finale finisse sur un zéro au lieu de 1. Eg si la longueur de string du Nom était 6 caractères alors ecx fera maintenant la boucle de 5 à 0 et la routine exécutera 6 fois.

ESI contient l'adresse du premier octet du (caractère) du NameBuffer quand ECX=0, ESI+ECX pointe le premier caractère et quand ECX=5, ESI+ECX pointe le caractère final.

La première instruction mov copie l'octet final dans le NameBuffer dans l'octet bas du registre d'edx (appelé dl) .

La seconde instruction mov copie cet octet dans le premier octet du SerialBuffer (stocké dans edi).

Ensuite la valeur d'edi est inc eleve à 1 pour indiquer au deuxième octet du SerialBuffer et va vers pour la boucle suivante de la routine .

Pour finir une opération logique OR Est exécuté sur ECX qui mettrait le flag à zéro si ECX avait atteint le zéro. L'instruction ja (jump if above) saute si le flag est zéro ,flag n'est pas mis. Autrement dit la routine garde la formation des boucles jusqu'à ce qu'ECX soit arrivé à 0 quand il continue sur à l'instruction ret et sort en arrière par la procédure de dialogue.

C'est une façon très simple d'écrire une fonction. Vous pouvez passer vos propres fonctions en un certain nombre de paramètres et avoir la fonction qui donne un résultat ou un succès / le flag d'erreur dans EAX comme les fonctions d'API le font. L'écriture d'un prototype pour la fonction autorisera l'utilisation d'invoke. Nous le découvrirons dans la page suivante de ce tutorial et personnaliserons notre keygen avec du graphisme et du son.


ARTeam Tutorial

Customisation d'un keygen

Nouveaux Concepts:

Ajout d'une ressource bitmap dans le Contexte d'une boîte de dialogue
Modification de la réponse et à mise à l'épreuve du clic de la souris
Utilisation des effets de transparence de Win2k/XP
Utilisation de Table Extractor de Thigo
Ajout d'un Module son d'extention (xm) chiptune
Écriture de fonctions plus sophistiquées - utilisation de prototypes, arguments et invoke

Nouvelles fonctions d'API:

SetWindowPos
GetWindowLong
SetWindowLong
SetLayeredWindowAttributes
ShowWindow
Sleep
wsprintf
lstrcat
RtlZeroMemory

Dans cette partie de ce tutorial nous personnaliserons notre keygen en ajoutant un contexte bitmap et changeant les Styles de Fenêtre de notre dialogbox et ses commandes pour nous débarrasser de la limite merdique et du titre et utiliserons des boutons transparents. Nous changerons aussi la conception légèrement et échangerons le bouton Generate pour un bouton About. Nous ferons générer le serial automatiquement et changera à chaque fois que le texte sera saisi par l'utilisateur.

Nous utiliserons comme exemple SuperCleaner 2.9 disponible ici: http://www.southbaypc.com/download/CleanSetup.exe

C'est plus commode par ce que la routine du serial est rippée de la cible presque directement sans faire aucun changement donc il est plus facile de voir comment il est converti pour notre keygen ici. Il y a des tutoriaux disponibles sur le site ARTEAM pour cracker les versions précédentes de cette cible et la routine du serial n'a pas changée depuis.

Avant que nous ne concevions quoique ce soit d'abord concevons notre boîte de dialogue et écrivons ensuite le code concernant les ressources. Nous commencerons par le logo ARTeam_Logo_4.bmp Et ajoutons du texte comme des labels pour l'editboxes et des boutons transparents. N'importe quel éditeur de photo devrait pouvoir faire l'affaire (j'ai utilisé ACDSEE). L'origine du fond pour les boutons vient du ("Fond d'écran du film Blade Runner ") il est conforme au texte sur le bitmap et a été téléchargé sur le Web gratuitement (pour installer de nouveaux fonds il faut les coller simplement dans le répertoire C\Windows\Fonts). J'ai renommé le résultat final en ARTeam_keygen2.bmp.

Décompressez les bitmaps du fichier zip dans votre dossier projet et créez un nouveau projet exe (standard) dans WinAsm. Ajoutez un nouveau script de ressource et dans l'éditeur de visual cliquez "Add New Dialog".

Changement de la Boîte de dialogue

Dans la barre de l'explorateur sous le label de la ressources, mettez en surbrillance le nouveau dialogue et éditez le Nom pour qu'il devienne IDD_KEYGEN. Double-cliquez et supprimez le champ du Titre. Maintenant double-cliquez sur le Style et assurez-vous que les entrées suivantes only (seulement) soient mises à (Yes) Oui : WS_POPUP, WS_VISIBLE et DS_CENTER.

Cela nous débarrassera de la limite et fera le dialogue non redimensionnable de plus cela apparaîtra toujours dans le centre de l'écran. Faites glisser maintenant le dialogue pour l'agrandir jusqu'à ce qu'il remplisse environ la moitié de l'écran. Cliquez sur le bouton d'image sur la barre d'outils dans la Boîte à outils et avec la Croix faites glisser un rectangle assez grand pour faire tenir notre bitmap.

IDC_IMAGE1002 apparaîtra dans la barre de l'explorateur de projet. Cliquez sur le bouton de la ressources sur la barre d'outils et Ajoutez ensuite la boîte qui apparaît. Naviguez jusqu'à ARTEAM_KEYGEN.BMP et ajoutez-le. Double-cliquez et éditez le champ du Nom au LOGO et le champ de l'ID à 2002 et cliquez sur OK. Mettez maintenant en surbrillance IDC_IMAGE1002, Cliquez l'image du menu choisissez le LOGO et glissez-le. Maintenant le bitmap apparaît sur le dialogue.

Finalement il est question de redimensionner le bitmap et le dialogue donc ils se couvrent sans se chevaucher avant l'ajout des commandes. Ajoutez 2 editboxes à côté de Name: et Serial:

Pour le Nom l'editbox change le nom en IDC_NAME et le style pour que le suivant soit seulement mis à (Yes) Oui: WS_CHILD, WS_VISIBLE, WS_BORDER, WS_TABSTOP, ES_AUTOHSCROLL, ES_CENTER

Pour le Serial l'éditbox change le nom en IDC_SERIAL, add ES_READONLY Au style et l'édition du texte pour y inclure "SuperCleaner 2.9 Keygen".

Finalement ajoutez 3 boutons poussoirs un pour Generate, Copy et Exit. Pour le premier, élargissez-le et positionnez-le Pour qu'il couvre le mot Generate sur le bitmap. Le nom Ensuite est modifié en IDC_ABOUT, Texte à About Et de style pour le suivant only Sont mis à (Yes) Oui : WS_CHILD, WS_VISIBLE, WS_CLIPSIBLINGS, WS_TABSTOP, BS_FLAT.

Faites enfin de même pour les 2 autres boutons et vous devriez voir ceci:

Quand ce dialogue est montré les boutons seront invisibles indiquant le texte ci-dessous. Comparez votre script de la ressource à celui de la copie dans la section de sourcecodes. Maintenant collez la source de ce tutorial dans le dossier ASM de votre projet.

Puisqu'il y a beaucoup de nouvelle information ici que je n'analyserai pas ligne par ligne mais je parlerai de chaque section séparément. D'abord la procédure de dialogue :

Il serait plus commode si notre nouveau keygen pouvait rester sur toutes les autres fenêtres comme ça le code pour celui-là serra assorti à l'autre code d'initialisation de procédure de dialogue après le message WM_INITDIALOG et utilisera la fonction API SetWindowPos :

invoke SetWindowPos,hWnd, HWND_TOPMOST,0,0,0,0,SWP_NOACTIVATE + SWP_NOMOVE + SWP_NOSIZE

Car notre keygen n'a aucune limite ou caption bar, nous devons pouvoir le déplacer en cliquant dessus n'importe où. Nous faisons cela en manoeuvrant la réponse à la souris frapper-examinant l'IE. nous dupons des fenêtres dans la pensée de cela toutes les fois qu'un clic de souris se produit dans le secteur client de la zone de dialogue qu'il se produit vraiment au-dessus du captionbar ! Le code pour cela entre également dans la procédure de dialogue :

.elseif uMsg==WM_LBUTTONDOWN
invoke SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0

Le code d'initialisation pour notre dialogue contient également l'appel à la fonction enchaînée et les fonctions Music-Play qui sont expliquées ci-dessous. Cliquez sur le bouton About cela affichera une MessageBox avec nos détails. La fonctionnalité de copy fonctionne comme il l'a été décrit dernièrement dans ce tutorial et en cliquant le bouton Exit cela appellera les fonctions à l'effacement et arrêtera la musique jouée avant de détruire la zone de dialogue.

La nouvelle partie de cette procédure de dialogue est impliquée en filtrant dehors des messages de l'editbox de sorte que chaque fois qu' un caractère est écrit nous puissions répondre en appelant notre procédure Generate et de mettre à jour le serial. Quand le message de WM_COMMAND est envoyé par un contrôle, le composant wParam est divisé dans 2 moitiés. Le mot d'ordre bas(de commande basse) spécifie l'identificateur du contrôle et le mot d'ordre haut(de commande haute) porte un code d'avis.

D'abord nous regardons le mot d'ordre bas(de commande basse) (dans ax) pour voir si le message est de l'editbox nommé (IDC_NAME) alors nous devons regarder le mot d'ordre haut(de commande haute). Puisqu'il n'y a aucune manière en assembleur de regarder seul le mot d'ordre élevé d'un registre de 32 bits, nous avons pour décaler la partie d'ordre élevé dans la partie d'ordre réduit et la lire comme ceci. SHR EAX, 16 accomplit ceci en décalant le contenu du juste d'eax par 16 bit

Maintenant ax contient le mot qui précédemment était d'ordre élevé. c'est EN_CHANGE puis l'editbox qui nous dis que que le texte a été changé ainsi nous pouvons appeler notre procédure Generate. Omettre la présente partie du code produit quelques résultats étranges avec l'appel de Generate en réponse aux clics de la souris, etc.

Effets de Disparition(Fading Effects)

Pour faire en sorte que notre keygen apparaisse en fondu sur son ouverture et sa fermeture, nous avons 2 procédures appelées FadeIn et FadeOut. Pour ceux-ci nous aurons besoin de 2 prototypes,une variable DWORD non initialisée " Transparence" et des constantes suivant ce que nous devons définir comme elles ne sont pas déjà dans le fichier windows.inc : LWA_ALPHA, LWA_COLORKEY, WS_EX_LAYERED and DELAY_VALUE.

Pour utiliser les fonctions de transparence de win2k ou XP (c'est-à-dire non disponible sur les versions précédentes de Windows), nous devons utiliser une fenêtre à plusieurs Couches. Cela peut significativement améliorer la rapidité de l'exécution et les effets visuels pour une fenêtre qui a une forme complexe, anime sa forme,ou si vous souhaitez utiliser l'alpha mélangeant des effets. Le système automatiquement compose et peint les fenêtres posées et les fenêtres d'applications fondamentales. En conséquence, les fenêtres posées sont rendues sans à-coup, sans clignotement typique des régions complexes de fenêtre. En outre, les fenêtres posées peuvent être partiellement translucides, c'est-à-dire, alpha mélangées. Afin de faire une fenêtre posée, la fenêtre doit être créée avec ou ensemble pour avoir le modèle de fenêtres étendues par WS_EX_LAYERED.

La procédure FadeIn ressemble à ceci:

L'API GetWindowLong récupère des informations sur une fenêtre. Le type d'informations est spécifié par 2ème param - dans notre cas GWL_EXSTYLE demande le style de fenêtres étendue pour la fenêtre et est rendu dans eax. La ligne suivante ajoute la valeur représentée par WS_EX_LAYERED à eax. La 3ème ligne met cette valeur comme le style de fenêtres étendue pour la fenêtre.

L'API SetLayeredWindowAttributes met l'opacité et la transparence de la clef de colore d'une fenêtre posée. Dans ce cas le paramètre final spécifie une transparence (le mélange alpha) le cadre(l'arrangement) et 2ème 0 spécifient complètement transparent. La ligne suivante montre la fenêtre dans un format non activé et la ligne suivante déplace une valeur de 75 dans notre variable de transparence.

La boucle suivante d'instructions incrémente la transparence par 5 et met la transparence de fenêtre avec la nouvelle valeur à chaque boucle jusqu'à ce que la transparence s'étende 255 (la valeur maximale = opaque). Le sommeil laisse(quitte) un petit retard entre chaque boucle pour que l'effacement n'arrivent pas trop rapidement. Quand la transparence = 255 que la procédure retourne.

La procédure FadeOut montre presque la même chose et fonctionne dans l'ordre inverse :

Critiques de cette procédure fade :

Notre fonction fade-in est appelée en réponse au message WM_INITDIALOG étant envoyé à la dialogbox par l'OS. Le code placé est ici donc exécuté avant que le dialogue ne soit montré sur l'écran. Le résultat de c'est qu'un dialogue est faded-in - avant que le contexte bitmap et les commandes n'apparaissent tandis que la fonction fade-out - ne subit pas ce problème. Un autre (beaucoup plus simple) la solution peut être d'utiliser l'API AnimateWindow pour faire l'alpha-blending.La ligne suivante devrait suivre le message de WM_INITDIALOG :

invoke AnimateWindow,hWin,1000,AW_BLEND+AW_ACTIVATE

Et la ligne suivante devrait suivre le message WM_CLOSE avant l'appel EndDialog :

invoke AnimateWindow,hWin,1000,AW_BLEND+AW_HIDE

Cependant dans notre cas, bien qu'il donne un effet fade-in, les boutons transparents ne sont pas bien traités et sont dessiné comme visible au premier plan.

Son (Sound)

Cette partie vous expliquera comment ajouter un chiptune (ceux-ci peuvent être téléchargés librement sur (de nombreux sites Web) et créer le son en utilisant uFMOD. C'est essentiellement un player de module de traqueur rapide en bas en minuscule qui intervient comme une bibliothèque (uFMOD.lib) avec juste une API à 4 fonctions. UFMOD des fonctions d'importations de winmm.dll pour que la bibliothèque de cette DLL puisse être liée dans notre code source. Les instructions pour utiliser uFMOD sont contenues dans le fichier d'ufmodapi.inc et Il y a 2 exemples de code source inclus dans la distribution. UFMOD est une grande amélioration sur l'ancien player miniFmod.

Il y a 3 façons d'ajouter un chiptune à notre application :

1. Comme une table de données hexa collé directement dans l'asm code ou dans un fichier d'inc
2. Comme les données brutes du type RT_RCDATA dans la section de ressource .
3. Comme un fichier de .xm complet inclus dans la section de ressource.

Dans cet exemple j'ai opté pour la première option comme c'est facile et empêche le chiptune étant extrait par la ressource taillant le logiciel. J'ai couvert un exemple de la façon d'utiliser RT_RCDATA brut dans la section des ressource dans tutorial sur l'utilisation de régions (voir < un href = "arteam%20asm_page9_region.htm" > ici ). Cette méthode pourrait facilement être utilisée ici pour stocker les données de fichier de xm dans la section de ressource de notre exécutable.

D'abord nous devons convertir notre fichier de xm dans une table et le sauver comme un fichier include. Pour cela nous utilisons l'Extracteur de Table de Thigo. Lancez l'outil et choisissez File > Load and browse to DEFJAM3.XM:

Cela vous dit la taille de fichier donc vous devez juste le copier dans la boîte de Taille vide et vous remplir dans les autres boîtes comme suit :

Comme nous voyons dans listbox, le fichier dumpé comme result.txt. Puisque les données ont été disposées comme un tableau d'octets hexa appelés "la table" nous devons juste le renommer en chiptune.inc et il est prêt pour être utilisé.

Pour utiliser uFMOD nous devons inclure ufmodapi.inc et 2 bibliothèques ufmod.lib et winmm.lib. Ensuite nous devons inclure nos données xm en forme de table dans .data la section directement ou dans un fichier include chiptune.inc comme dans notre exemple (inclus dans la section de code source) .

Notez que chiptune.inc contient juste un tableau énorme de valeurs de taille hexa d'octet appelées "la table". Vous vous rappellerez qu'utilisant include est le même comme tapant du tableau directement dans le code. Car le PlaySong fonctionne nous devons savoir la taille des données xm donc la ligne suivante spécifie la taille dans la variable xmSize.

Le $ est défini dans les guides programmeurs MASM comme l'opérateur d'adresse actuel ou la valeur actuelle du compteur d'emplacement . L'une ou l'autre voie, soustrayant l'adresse du premier octet dans le tableau de table du $ donne la taille dans des octets .

Finalement nous devons ajouter des appels à la fonction de PlaySong quand la boîte de dialogue est initialisée et détruite. Cette fonction est définie dans ufmodapi.inc comme suit :

Puisque notre fichier de xm est inclus comme un tableau de données initialisées, il est chargé dans la mémoire quand l'exécutable est exécuté, ainsi le 3ème paramètre est XM_MEMORY. 2ème param est donc la taille des données dans la mémoire ou xmSize et le 1er paramètre sont un indicateur sur les données (l'adresse du premier octet de notre tableau). Pour arrêter le chiptune le jeu nous appelons simplement PlaySong de nouveau avec 0 paramètres.

La Procédure Generate

L' algorithme de la génération du serial commence en obtenant texte entré dans la boîte de Nom et le stockant dans NameBuffer. L'adresse de NameBuffer est copiée dans EDI et le nombre de caractères dans NameBuffer (et dans la boîte de Nom) est calculé et copié dans ESI. Le nombre de caractères est évalué pour voir si c'est le zéro. Si c'est le zéro la variable SerialBuffer et la boîte serial sont vidés et les retours ordinaires. Si a là été saisi, donc le commencement du calcul du serial.

Il y a 4 boucles, chaque addition ou multiplication notre nom avec une différente constante et l'enchaînement les résultats ensemble pour faire un serial de 4 sections.

Souvenez-vous à cette étape qu'EDI pointe au début de la string le Nom et ESI contient le nombre de caractères et qu'EAX et ECX ont été mis à zéro. La première constante (Hash1) est chargée dans EDX. Le premier octet de la string du Nom est copié (MOVSX == le mouvement avec le signe s'étend; regardez cela en haut si vous êtes intéressés) dans EBX et la valeur de gâchis est ajoutée.

Une fois que la section finale du serial a été calculé qu'il est ajouté aux autres parties et le serial, le résultat est montré dans la boîte du serial. J'ai inclus le désassemblage du code cible dans Ollydbg et montré ci-dessous pour que vous puissiez voir comment peu il différé de notre code d'asm ci-dessus :


ARTeam Tutorial

Utilisation de Régions et Données de Ressource

Nouveaux Concepts:

Régions et utilisation de RGN Générateur
Utilisation de données de ressource brutes
Utilisation de Brush
Répondant au Double-clique du bouton gauche de la souris

Nouvelles Fonctions API:

LoadBitmap
CreatePatternBrush
FindResource
LoadResource
SizeofResource
LockResource
ExtCreateRegion
SetWindowRegion
DeleteObject

Ce Tutorial nous montre comment réaliser des régions complexes et qui sont habituellement utilisées pour faire des formulaires de fenêtre personnalisées pour être appliquées à notre keygen ou à notre patch. La plupart des tutoriaux existants, pour ses formulaires de fenêtre personnalisées et des régions, sont appliquées aux thèmes des fenêtres entièrement pleines. Ici je n'ais pas pensé à vous donner un exemple concernant le cracking, donc je l'ai adapté au Tutorial N° 25 d'Iczelion (voir la section de référence) et montré comment cela peut être appliqué à une boîte de dialogue simple pour notre style rapide et facile en programmation.

Pour faire une fenêtre ou une boîte de dialogue en custom prenant une forme complexe, nous devons définir une région pour le plan de notre forme et ajouter ensuite la région à la fenêtre. Une région est un rectangle, un polygone, ou une ellipse (ou une combinaison des deux ou d'autres formes) qui peut être rempli, peint, inversé, encadré et utilisé du coup, pour exécuter la mise en teste (évaluant sur l'emplacement du curseur).

Il y a des fonctions API pour créer des régions simples (rectangulaire, elliptique ou polygonal) et combiner aussi de telles régions ensemble. Si nous voulons une forme plus complexe nous devons utiliser une région étendue. L'API pour créer une région étendue prend un indicateur sur un bloc de données de région :

HRGN ExtCreateRegion(
CONST XFORM *lpXform, // L'indicateur pour transformer des données
DWORD nCount, // La taille de structure contient des données
CONST RGNDATA *lpRgnData // Indicateur sur les données de région
);

Ces données de région consistent en un grand nombre (beaucoup de centaines) de régions rectangulaires combinées pour définir notre forme complexe. Heureusement nous ne devons pas calculer ces données de région nous-mêmes, car il y a un utilitaire appelée le Générateur de Région par Richard D'Oude qui le fera pour nous et la production des données nous donnera un fichier avec l'extension .rgn qui pourra être inclus dans notre exécutable comme une ressource.

Pour utiliser des données de ressource, nous devons trouver la ressource, charger la ressource, trouver la taille de la ressource et fermer finalement la ressource. Cela nous laisse avec un indicateur sur les données de ressource en mémoire qui peut alors être utilisée selon notre programme.

D'abord nous devons préparer un bitmap en choisissant un secteur dont nous voulons qu'il soit transparent et le faire se remplir avec une couleur particulière. Cela peut être fait dans n'importe quel éditeur de photo. J'ai utilisé l'exemple du Tutorial d'Iczelion (tweety78.bmp) qui est inclus dans les fichiers de ressource.

Ensuite nous devons créer nos données de région. Lancez le Générateur RGN, cliquez sur Browse pour choisir le bitmap et le Choix de cliquer pour ouvrir le panel de couleur. Vous verrez 2 places sur la droite - la supérieur indique la couleur qui sera actuellement transparente (noir par défaut) et l'inférieur est la couleur indiquée par la pipette (le pointeur de souris). Déplacez la souris sur un secteur rose et faites un clique avec le bouton droit pour choisir rose comme pour la couleur transparente. Cliquez finalement sur OK, Créant alors et sauvegardant votre fichier de région en region.rgn :

Ouvrir WinAsm et créez un nouveau projet et des scripts de ressource. Dans le nouveau script de ressource on ajoute un dialogue avec une taille assez grande pour contenir le bitmap. Changez ensuite les Styles pour que seulement WS_POPUP, WS_VISIBLE et DS_CENTER soient mis. Ajoutez un bouton carré minuscule avec un "?" pour son titre. Le bouton doit être placé sur une partie non transparente de l'image ou ce ne sera pas visible - cela peut demander un peu d'expérience car la position doit être correctement faites sinon vous ne pourrez pas voir la forme de l'image en lançant le Débuggeur (ou Lecteur):

L'autre alternative devrait ajouter l'image d'un bouton sur le bitmap comme nous avons fait dans le dernier Tutorial. Ajoutez ensuite le bitmap et les autres ressources du fichier de région mais ne changer en aucun cas ceux des noms. Dans ce cas le bitmap a 2001 ID et les données de région ont 2002 ID. Le script de ressource et le fichier en asm (region.rc et region.asm) sont inclus comme d'habitude dans la section du codes source. A coller dans le fichier en asm :

Le susdit devrait être très évident, mais dans la note, nous n'avons pas initialisé la variable prête pour traiter les données de ressource et un brush (le contexte bitmap). La procédure de dialogue est comme suit :

La première nouvelle ligne utilise LoadBitmap qui prend la ressource ID de notre bitmap (2001) et lui retourne un handle que nous pouvons ajouter à CreatePatternBrush. Un model de brush est simplement un bitmap que l'OS peut utiliser pour peindre l'intérieur des formes remplies. CreatePatternBrush retourne un handle au brush (le bitmap) pour lequel nous stockons dans une variable plus tard. Pour peindre notre dialogbox avec ce brush, nous devons répondre au message WM_CTLCOLORDLG et retourner le handle à notre brush. Le message WM_CTLCOLORDLG est envoyé à une boîte de dialogue avant que le système ne fasse apparaitre la boîte de dialogue pour permettre de mettre son texte et ses couleurs de fond.

Nous avons ajouté nos données de région à l'exécutable comme des données de ressource brutes. Pour l'appliquer à notre dialogue, nous devons d'abord créer une région de fenêtre étendue et la mettre ensuite à notre dialogue. Pour créer une région étendue, nous utilisons ExtCreateRegion qui prend un indicateur sur nos données de région et sa taille, puis retourne un handle à la région étendue finie que nous passons alors à SetWindowRegion.

Pour avoir accès à nos données de région nous devons trouver la ressource de son ID. Nous pouvons alors trouver la taille, le charger et le fermer ensuite pour obtenir un indicateur sur les données comme indiqué ci-dessus. On peut alors passer l'indicateur et la taille à ExtCreateRegion comme déjà dit.

C'est la façon standard d'avoir accès aux données de ressource et ne dépend pas du type de données en question. Si nous avions inclus un fichier audio comme des données de ressource brutes, comme dans le dernier Tutorial, alors nous y aurions accès exactement de cette façon.

Puisqu'il n'y a aucun bouton Exit dans notre fenêtre, il ne serait pas élégant de devoir la fermer en appuyant sur Alt+F4, donc nous tiendrons compte d'un Double-clique sur le bouton gauche de la souris pour Quitter. Ceci est accompli en traitant le message WM_LBUTTONDBLCLK comme indiqué ci-dessus.


ARTeam Tutorial

Patcheur & Offset

Nouveau Concepts:

L'utilisation des contrôles ListBox et CheckBox dans votre dialogue
Ouverture(Opening), Lecture(Reading), Ecriture(Writing) et Fermeture(Closing) des fichiers
L'Indicateur de Fichier

Nouvelles fonctions API:

CreateFile
CopyFile
SetFilePointer
ReadFile
WriteFile
CloseHandle

Dans ce tutorial, nous coderons un patcher autonome simple qui ouvre un fichier cible dans le dossier actuel du logiciel(le patch doit être placé dans le même dossier que la cible), qui contrôlera sur une certaine séquence d'octets à un offset donné et les recopie. Ceci est codé dans le patch à un offset correspondant. Si la séquence d'octets correct n'est pas trouvé, alors le patcher annoncera une erreur.

Il devrait aussi nous notifier si le fichier a déjà été patché. Il devrait y avoir une option pour faire une sauvegarde du fichier cible avant le patchage et si les attributs de fichier cible incluent seulement la lecture seule, cela devrait être mis en Lecture Ecriture. D'abord nous devons couvrir une certaine théorie.

Ouverture d'un fichier

La fonction de CreateFile crée ou ouvre un fichier et retourne un handle qui peut être utilisée pour avoir accès au fichier. Il peut aussi être utilisé pour ouvrir des objets incluant des dispositifs de disque, des consoles et des répertoires. Le paramètre dwCreationDisposition contrôle son comportement :

CREATE_NEW - Crée un nouveau fichier, échoue si le fichier nommé existe déjà
CREATE_ALWAYS - Crée le nouveau fichier, recopie n'importe quel fichier existant de même nom
OPEN_EXISTING - Ouvre le fichier, échoue si le fichier n'existe pas
OPEN_ALWAYS - Ouvre le fichier, crée un nouveau si le fichier n'existe pas

Chaque fichier ouvert a un indicateur de fichier qui spécifie l'octet suivant à être lu ou l'emplacement pour recevoir l'octet suivant a être écrit. Quand un fichier est ouvert pour la première fois, windows place l'indicateur de fichier au début du fichier. Comme chaque octet est lu ou écrit, windows avance l'indicateur de fichier. Une application peut aussi déplacer l'indicateur de fichier en utilisant la fonction de SetFilePointer.

Lire et écrire dans un fichier

Une application lit et écrit dans un fichier en utilisant le ReadFile et les fonctions de WriteFile qui exigent le handle du fichier à être ouvert comme ci-dessus. ReadFile et WriteFile lisent et écrivent nombre indiqué d'octets à l'emplacement indiqué par l'indicateur de fichier. Les données sont lues et écrites exactement comme donné; les fonctions ne formatent pas les données.

Quand l'indicateur de fichier atteint la fin d'un fichier et l'application essaye de lire la fin du fichier, aucune erreur n'arrive, mais aucun octet n'est lu. Donc, lisant des octets zéro sans une erreur signifie que le programme a atteint la fin du fichier. L'écriture d'octets zéro ne fait rien.

Fermeture du fichier

La fonction de CloseHandle ferme et invalide les handles, puis sort des ressources du système pour presque tous les objets. Le système fermera toutes les handles ouvertes à la fermeture du fichier, mais c'est toujours une bonne solution pour des programmes qui ferment leurs handles avant la fin.

Exemple de notre fichier cible

Nous utiliserons de nouveau SuperCleaner 2.9 comme notre exemple de cible, mais je ne discuterai pas comment le forcer, mais seulement comment coder le patcher. La section de code cible dans SuperCleaner.exe est a considéré dans Olly comme ce qui suit :

Nous devons patché cette ligne :

0042374F 75 41 JNZ SHORT SuperCle.004234C2

avec cette modif:

0042374F EB 41 JMP SHORT SuperCle.004234C2

Cela implique le changement seulement de 1 octet (75 à EB) à RVA 2347F (ImageBase de 400000 soustrait de VA pour obtenir RVA - voir mon tutoriel sur le PE pour plus de détail). Nous pourrions maintenant convertir le RVA de l'adresse du patch dans la mémoire à un offset dans le fichier sur le disque pour le patcher (de nouveau voir mon tutorial sur le PE) . Une méthode plus facile doit permettre de rechercher dans le fichier avec un éditeur Hexa pour trouver la séquence correct.

Autant que la recherche d'une séquence d'octets uniques est concernée, il y a 5 cas de 75 41 dans le fichier cible, mais seulement 1 cas de 75 41 E8 BA, donc nous utiliserons un DWORD pour notre séquence cible. Dans ce cas, comme les choses sont faciles, le RVA de l'adresse du patch est le même que l'offset sur le disque. Si l'offset attendue est trouvé, notre fichier cible doit être la version correcte.

Nous coderons une petite application(notre patch) à base de dialogue avec 3 boutons pour Apply(Appliquer), About(ou A propos de...) et Exit(Sortir ou Fermer). Nous utiliserons une case à cocher(CheckBox) pour créer une sauvegarde et une liste de choix(ListBox) à la production de notre succès ou des rapports d'erreurs, qui sont plus commodes pour l'utilisateur, que le besoin de cliquer sur OK sur de nombreuses MessageBox. Ouvrir WinAsm comme d'habitude et ajoutez un script de ressource. Faites-le comme cela :

La conception est inspirée par les patchs produits par dup2 (Diablo2oo2's Patcher Universel). J'ai utilisé un Logo (inclus dans ces archives) mais le patcher peut être entièrement personnalisé comme dans l'exemple keygen. En positionnant la case à cocher, vous devez traîner un rectangle assez grand pour l'étiquette de texte ainsi que la case à cocher elle-même. Le script (patcher.rc) est inclus dans la section de code source.

Maintenant, coller le code de offsetpatcher.asm dans WinAsm :

Remarquez au-dessus des 2 prototypes de fonction, pour notre fonction de patch et une petite routine pour mettre à jour notre liste. Remarquez aussi notre séquence d'octets cible, patch des octets et l'offset dans la section .data. Les séquences d'octets sont en réalité des tableaux - nous discuterons de ceux-ci dans le chapitre Structure des Données.

NOTE: Des nombres hexadécimaux doivent toujours commencer par un chiffre (0-9) décimal. Si nécessaire, ajoutez un zéro principal pour distinguer entre des symboles et des nombres hexadécimaux plutôt que le début avec une lettre.

Nous avons aussi le nom de notre cible et le nom que nous utiliserons pour le fichier de sauvegarde. Dans la section du .data ? nous avons des buffers pour lire dans les octets à l' offset cible et aussi prendre le nombre d'octets lus et écrits par les Fonctions de WriteFile et de ReadFile. La procédure de Dialogue devrait être surtout évidente, mais contient des appels à nos 2 Listes de fonctions et Patch :

La fonction de Liste prend un indicateur sur la String à être imprimé dans la liste et envoie des messages à la Liste(ListBox) pour ajouter(add) la String et aussi faire défiler pour garder le nouveau message en vue :

Les fonctions de démarrage du Patch vérifiées les attributs du fichier cible pour assurer qu' ils ne sont pas en lecture seule (FILE_ATTRIBUTE_READONLY). Si ils sont en lecture seiule, alors l'instruction suivante les fait mettre en Lecture-Ecriture ( FILE_ATTRIBUTE_NORMAL) pour permettre au fichier d'être écrit.

Ensuite nous essayons d'ouvrir le fichier avec CreateFile. Si le fichier est déjà utilisé (ouvert) nous obtenons une erreur de handle invalide, donc nous l'annonçons et Exit(Sortir ou Fermer). Si couronné de succès (pas d'erreurs) nous regardons la case à cocher. Si vérifié nous copions le fichier et le rebaptisons à notre nom de secours et annonçons la sauvegarde couronnée de succès dans la liste(ListBox).

Finalement nous utilisons SetFilePointer avec l'offset du patch et lisons 4 octets pour voir si nous avons la version cible correcte, la version incorrecte ou un fichier déjà patché. Si nous avons la version de fichier correcte nous retardons l'indicateur de fichier sur le début de l'offset du patch de nouveau et utilisons WriteFile pour patcher notre octet cible, rapport erreurs ou le succès dans la liste(ListBox) avant le retour.

NOTE: Le caractère d'antislash peut être utilisé pour recouvrir de longs arguments pour faire fonctionner plus d'appels de plusieurs lignes. Ici l'appel à CreateFile a un argument par ligne.

Ces routines ordinaires sont bien mais ... et si une nouvelle version du fichier cible est sortie dans lequel la protection est exactement la même mais les offsets ont changés légèrement ? Dans le tutorial suivant, nous améliorerons notre patcher en ajoutant la fonctionnalité pour parcourir le fichier pour la séquence d'octets cible à un offset différent pour permettre de créer un patch couronnée de succès. De plus nous regarderons la mémoire dressant la carte d'un fichier qui est une alternative beaucoup plus lisse à l'utilisation de l'indicateur de fichier.


ARTeam Tutorial

Patcheur Cherche & Remplace

Nouveau Concepts:

Fichiers planifiant la mémoire(RAM)
Instructions de string en assembleur
Utilisation de variables locales dans vos procédures
Retour de codes avec succès/erreur de codes

Nouvelles fonctions API:

CreateFileMapping
MapViewOfFile
UnmapViewOfFile
CloseMapFile

Dans ce tutorial nous rendrons notre exemple précédent plus puissant, en y ajoutant une fonctionnalité pour pouvoir ouvrir un fichier et pour que l'ordre des octets restent corrects, de façon à ce que les changements mineurs apportés à la version du programme par le patch n'affectent pas la routine de protection. D'abord, Nous devrons encore recourir à une certaine théorie.

Fichiers planifiant la mémoire(RAM)

Le File Mapping (Configuration d'un fichier) est une manière de regarder le contenu d'un fichier sans devoir l'assigner à un grand bloc mémoire et sans le charger entièrement. Ce fichier peut être n'importe quel fichier sur le disque que vous pourrez tracer dans la mémoire ou il peut être aussi un fichier d'un dossier système.

La première étape est de créer un fichier qui peut se composer de l'ensemble ou d'une partie d'un fichier. Il est placé dans un dossier sur le disque. Ceci signifie que quand le système permutent hors de son dossier le fichier, tous les changements faits au fichier sont écrits dans le dossier sur le disque. Quand le fichier est permuté dans son dossier il est reconstitué à partir du dossier.

La taille du fichier est indépendante de la taille du fichier de destination. Si le fichier source est plus petit que le fichier de destination, le système se préoccupera seulement du nombre indiqué d'Octets à partir du fichier. De Créer un fichier, cela ne prend pas de la mémoire physique (RAM), cela la réserve seulement. Il est donc d'usage d'en créer un de la même taille que le fichier même si vous ne comptez pas regarder le fichier entier ainsi ça n'engendrera aucun débordement dans les ressource du système.

Cependant, si le fichier est plus grand que le dossier, le système augmente le dossier en apposant le remplissage nulle avant que CreateFileMapping retourne. Les fichier sont statiques dans la taille ; une fois qu'ils sont créés, leur taille ne peut ni augmenter ni diminuer.

La prochaine étape sera pour tracer une vue de dossier (view file) de cet objet dans l'espace adresse (mémoire virtuelle) de votre Application. Une vue de dossier peut se composer de tout ou seulement d'une partie de dossier traçant l'objet, mais ne peut pas être plus grande. La fonction de MapViewOfFile renvoie un pointeur à la vue de dossier (view file). En dé référenciant le pointeur de l'application, on peut lire des données à partir du dossier et écrire des données au dossier.

L'écriture de la vue de dossier (view file) donne les résultats dans les changements au dossier traçant l'objet. L'écriture du dossier sur le disque est traitée par le système. Les données ne sont pas réellement transférées alors le plan du dossier de l'objet est écrit. Au lieu de cela, une grande partie de l'entrée de dossier et l'action (entrée-sortie) sont cachés pour améliorer l'exécution du système général. Cela peut être ignoré par la fonction de FlushViewOfFile qui copie le nombre indiqué d'octets de la vue de fichier(view file) au fichier physique immédiatement, sans attendre ceux qui sont cachés, pour arriver à écrire l'opération.

Quand un processus a fini avec le fichier dressant le plan de l'objet, il doit détruire toutes les vues de fichier dans son espace d'adresse en utilisant la fonction UnmapViewOfFile pour chaque vue de fichier. Si n'importe laquelle des pages de la vue de fichier a changé, depuis que la vue du plan a été dressée, le système écrit les pages changées du fichier au disque utilisant caching.

La configuration de fichier fournit plusieurs avantages:

1. Plus rapide et accès du fichier plus facile.
2. Seulement la partie nécessaire d'un fichier doit être planifier dans la mémoire
3. Mémoire Partagée entre deux ou plusieurs applications.
4. Aucun besoin d'utiliser les fonctions d'API de la mémoire et des fonctions d'API séparées d'entrée-sortie du Fichier.

La configuration du fichier permet à un processus d'avoir accès aux fichiers plus rapidement et facilement en utilisant un indicateur sur une vue de fichier. L'utilisation d'un indicateur améliore l'efficacité parce que le fichier réside sur le disque, mais la vue de fichier réside dans la mémoire.

Il permet aussi au processus d'efficacement fonctionner avec un grand fichier de données, comme une base de données, sans devoir dresser le plan du fichier entier dans la mémoire . Quand le processus a besoin de données d'une partie du fichier autre que ce qui est dans la vue de fichier actuelle, il peut ne pas dresser le plan de la vue de fichier actuelle, dressant alors le plan dans une nouvelle vue. La configuration du fichier est aussi utilisée comme le moyen de partager.

Le chargeur(loader) de Windows utilise la configuration de fichier pour charger des fichiers de PE dans la mémoire. C'est très commode depuis seulement les parties nécessaires, qui peuvent être lues sélectivement du fichier sur le disque. Sous Win32, vous devriez utiliser le fichier dressant le plan autant que possible.

L'interface utilisateur est exactement le même que le tutorial si on réutilise juste le même script de ressource. Collez le code de SnRpatcher.asm dans WinAsm:

La procédure de Liste est exactement la même que la dernière fois et la procédure du patch est très semblable avec une certaine disposition faite pour appeler la procédure de Feuilletage si l'ordre d'octets cible n'est pas trouvé à l'adresse attendue :

Le procédé de balayage trace simplement dans une vue du dossier et l'ordre du balayage octet par octet sont très intéressant. Quand un match est trouvé pour le premier octet, l'octet suivant est comparé à notre ordre de cible. Si c'est un match, alors que l'octet suivant est comparé mais que le deuxième octet n'est pas un match, le balayage continue. Une variable locale est employée pour stocker les résultats retournés par la routine dans eax. Ces questions sont discutées plus en profondeur ci-dessous :

Variables Locales

Des variables locales sont définies avec le nom suivant, LOCAL de syntaxe : la taille which doit être sur la ligne juste après le rapport de proc. À la différence des variables globales que nous sommes habitués à employer, elles ne peuvent pas être initialisées en tant qu'élément de définition. Dans l'exemple ci-dessus une instruction de mouvements est utilisée comme moyen d'initialiser le flag qui sera mis à 0. Employer des variables locales a l'avantage qu'elles peuvent seulement être vu par le procédé, qu'elles sont dans le procédé et sont détruites quand le procédé retourne.

Il est également facile de les utiliser en tant que flag de commande pour que MASM obtienne un bon empilage. Le chargement de l'adresse des variables locales doit être fait différemment des globals. Puisque l'adresse est une valeur sans aucune signification réelle sur la structure de l'empilage provisoire (pourquoi provisoire me direz-vous ? Et bien par ce qu'en ASM , la pile (Stack) est utilisée pour empiler et dépiler des valeurs dans les registres comme EAX,ECX etc...),l'opérateur de cette valeur ne peut pas renvoyez l'adresse donc l'instruction lea doit être utilisée à la place.

Instructions de String Assembleur

Il y a 5 instructions de string pour le traitement rapide et efficace des strings entières et de rangées. Le terme "instructions de string" se rapporte à un ordre de tous les éléments, non pas des chaînes de caractères simplement. Ces instructions travaillent directement sur toutes les rangées d'octets, de mots, et de dwords. Tous ont la syntaxe semblable :

MOVS - Pour copier et déplacer en rangeant des données d'un secteur de mémoire à un autre .
SCAS - pour chercher ("parcourir") en rangeant des données en mémoire et en les comparants à la valeur dans eax.
CMPS - pour comparer des données de tableau et indiquer l'adresse correspondante après un non (négation).
STOS - pour remplir des données de tableau par "Stockage" de la valeur dans eax dans chaque élément.
LODS - pour charger des données de tableau dans eax.

Chaque instruction de string fonctionne à la fois sur un élément de tableau et a des formules mnémoniques finissant avec b, w, ou d (l'octet, le mot ou dword) pour spécifier la taille de l'élément de string à être exploité. Ces instructions de string exigent souvent trois registres pour des opérandes :

ESI - s'approvisionnent en adresse de données de tableau
EDI - l'adresse de destination de données de tableau
ECX - le compte d'élément (facultatif)

Ils dépendent aussi de la valeur du flag de direction dans les flags enregistrés et peut utiliser tout ou une partie d'EAX.

Les incréments d'instruction ou les décroissances SI et DI après un élément de string, ont l'accès pour qu'ils l'indiquent à l'élément de tableau suivant. Le flag de direction détermine si SI et DI sont incrémentés (le flag clair, 0) ou décrémentés (le jeu de flag, 1). La taille de l'instruction détermine si SI et DI sont changés par 1, 2, ou 4 octets.

Tous, sauf LODS, peuvent être utilisés avec les préfixes d'instruction de répétition (LODS n'est pas utilisé avec un préfixe de répétition, depuis continuellement, le rechargement EAX avec la même valeur ne sert à aucun but). Le préfixe de répétition cause l'instruction qui le suit pour se répéter automatiquement pour le nombre de temps indiqués dans ECX ou jusqu'à ce qu'une condition devienne vraie:

REP - Instruction de répétitions de temps CX
REPE (REPZ) - Le maximum d'instruction de répétitions de temps CX tandis que les valeurs sont égales
REPNE (REPNZ) - Maximum d'instruction de répétitions de temps CX tandis que valeurs non égales

Dans le susdit, exemple utilisant repnz scasb, l'adresse de notre ordre d'octets cible est chargée dans ESI, l'adresse de la vue de fichier est chargée dans EDI et le nombre d'octets dans le fichier est chargé dans ECX. Ensuite le premier octet de notre ordre cible est copié dans EAX. C'est parce que SCAS fonctionne en parcourant par EDI la recherche de la valeur dans EAX. repnz scasb continue à parcourir la boucle par EDI tant que la valeur du compteur n'est pas atteinte (la RÉPÉTITION bien que n'étant pas le Zéro ou Non Égal) ou jusqu'à ce que ECX frappe le zéro (la fin du fichier) pour que la routine s'arrête. Dés que la valeur du compteur est atteinte, il arrêtera de parcourir la boucle et l'état actuel (ESI, EDI et ECX) est sauvé sur la pile et la partie comparante des démarrages ordinaires.

EDI est diminué de 1 (comme instruction de scas arrêtée après la valeur du compteur), ECX est placé (à la longueur 4 de notre ordre de cible) et ESI contient toujours l'adresse de notre ordre d'octet de cible. Le cmpsb de Repz compare des octets successifs dans ESI et EDI jusqu'à ce qu'ils soient identiques (la comparaison se répéte jusqu'à ce que ce soit à zéro ou égal) et des arrêts s'il y a une disparité. Si ECX a réduit à 0 quand les CMPS s'arrête, ça signifie que chacun des 4 octets a été assorti et la routine stoppe. Si ECX>0 (IE moins de 4 octets assortis) alors l'état précédent est sauté outre de la pile et la routine de balayage reprend. L'action précise que chacune des instructions de string doit être illustrée par des exemples ainsi je recommande d'obtenir un des ebooks de la page de référence à la fin de ce cours d'instruction et de le lire du début jusqu'à la fin correctement. Les limitations de la conception ce Patcher sont très simples. Il y a seulement 1 octet à raccorder et nous avons pris le temps de s'assurer que notre ordre d'octet de cible seulement s'est produit par le passé. Plus de code serait nécessaire si les patchs multiples rapportées étaient nécessaires ou si l'ordre d'octet se produisait plus d'une fois, mais seulement un exemple avait besoin d'être raccorder. Diablo2oo2, qui est l'auteur du plus excellent des patchers (dup2) a édité le sourcecode pour la recherche et remplace le moteur sur son site Web. C'est une très belle routine très et mérite un regard. J'ai inclus un instantané de lui ici pour la référence rapide mais elle peut être téléchargée dans un fichier ZIP ici :

http://diablo2oo2.di.funpic.de/downloads/dup.search.and.replace.patchengine.sourcecode.rar


ARTeam Tutorial

Structures de données

Nouveaux Concepts:

Stockage de données
Variables
Indicateurs
Caractère indirect & Non référencer(Dereferenced)
Tableaux, Structures & Unions

Avant d'aller plus loin, il est nécessaire de comprendre les structures de données comme ceux-ci peuvent sembler intimidant, mais sont importants dans la programmation de Win32 et le reversing. Par exemple les données qui composent les en-têtes de fichiers exécutables portent ses contenues dans des structures diverses, si pour coder n'importe quel application qui manipule le PE des fichiers, vous devez savoir comment les structures de données sont traitées dans masm. SEH (Structured Exception Handling = le Traitement d'Exception Structuré) qui figure dans quelques systèmes de protection, utilise aussi des structures. Nous avons rencontré par hasard le startupinfo et les structures de process_information dans le dernier Tutorial. Dans le suivant nous utiliserons la structure d'openfilename et travaillerons avec des en-têtes PE.

Stockage de Données en Mémoire

Bien qu'une unité de mémoire dans des ordinateurs modernes tienne un octet, les unités de données utilisées dans un programme sont souvent beaucoup plus grandes. Avant que n'importe quelles données ne peuvent être stockées dans la mémoire, vous devez dire à l'ordinateur combien d'espace il faut réserver en utilisant des données abstraites types. Cela spécifie que la quantité de mémoire a eu besoin de stocker des données et fait en sorte que des données y seront stockées dans cet emplacement de mémoire. Il y a 2 types de types de données abstraits; Des types de données primitives et des types de données définis par l'utilisateur.

Des types de données primitives sont créées dans un language de programmation. Par exemple, masm utilise les types suivants, le nombre entier et la variable :

BYTE 8 bits Nombres non signés de 0 à 255
WORD (2 bytes) 16 bits Nombres non signés de 0 à 65,535 (64Kb)
DWORD (4 bytes) 32 bits Nombres non signés de 0 à 4,294,967,295 (4Mb)
SWORD (Word signé) Nombres signés de -32,768 à +32,767
SDWORD (DoubleWord signé) Nombres signés de -2Mb à +2Mb

Des types de données définis par l'utilisateur sont les groupes de types de données primitives définis par le programmeur et incluent des String, des tableaux, des structures et des unions dans lesquelles vous pouvez avoir accès aux données comme une unité ou comme les éléments individuels qui composent l'unité.

Tandis que les variables peuvent se terminer et être placé n'importe où dans la mémoire, les éléments dans des types de données définis par l'utilisateur sont l'un à côté de l'autre dans la mémoire. Il est plus efficace d'indiquer pour ranger des éléments que des variables parce que vous pouvez indiquer à l'élément de tableau suivant en vous déplaçant simplement à l'emplacement de mémoire suivant. Nous discuterons des tableaux et des structures plus en détail bientôt.

Caractère indirect et Non référencer(Dereferenced)

Il est important de comprendre la distinction entre le CONTENU d'une variable (sa valeur) et l'ADRESSE d'une variable (son emplacement dans la mémoire).

Quand vous vous référez à une variable vous faites la référence DIRECTE, mais quand vous vous référez à l'ADRESSE d'une variable, vous faites la référence INDIRECTE.

L'ayant de l'ADRESSE d'une variable, établit "un" niveau de CARACTÈRE INDIRECT. Quand vous traitez avec des tableaux vous pouvez avoir un tableau d'adresses. Si vous utilisez l'ADRESSE de ce tableau pour avoir accès à la VALEUR d'une variable et de son adresse dans le tableau vous avez un niveau supplémentaire de caractère indirect.

La raison de pourquoi les niveaux de caractère indirect sont si utiles, est qu'une adresse de DWORD simple ou un INDICATEUR peuvent avoir accès à un beaucoup plus grand nombre de variables très rapidement.

NOTE: Un indicateur est simplement une variable qui contient une adresse comme sa valeur.

Il est possible d'enlever un niveau de caractère indirect d'une adresse en l'incluant dans des parenthèses carrées. Cette technique est appelée SANS REFERANCE(DEREFERENCING). Dans l'exemple suivant eax est utilisé comme un indicateur sur la variable MyVar et est sans référence(dereferencing) dans ebx pour donner la valeur de la variable :

.data
MyVar dd 77h

.code
mov eax,offset MyVar ; eax contient maintenant l'adresse de MyVar
mov ebx,[eax] ; ebx contient maintenant 77h

Voir Ici Pour les explications à propos de [] syntaxe.

TABLEAUX

Comme dit ci-dessus, un tableau est une collection séquentielle de variables, toutes de la même taille et de même type, appelées des éléments. Vous pouvez avoir accès à tous les éléments dans un tableau autant qu'au premier élément. Tandis qu'un tableau est identifié d'un nom, chaque élément dans un tableau en fait référence avec un nombre d'indice, commençant par le zéro. L'indice de tableau apparaît entre parenthèses carrées après le nom :

Tableau[9]

Dans l'assembleur l'indice d'un élément se réfère au nombre d'octets entre l'élément et le début du tableau. Cette distinction peut être ignorée pour les tableaux d'éléments en taille d'octet, puisque le nombre de position d'un élément correspond à son indice. Par exemple, définissant un tableau d'octets a appelé "prime" :

prime Octet(byte) 1, 3, 5, 7, 11, 13, 17

Donne une valeur de 1 à prime [0], une valeur de 3 à prime [1], et ainsi de suite. Cependant, dans des tableaux avec des éléments plus grand que 1 octet, les nombres d'indice (sauf le zéro) ne correspondent pas à la position d'un élément. Vous devez multiplier la position d'un élément de sa taille pour déterminer l'indice de l'élément. Ainsi, pour le tableau

wprime WORD 1, 3, 5, 7, 11, 13, 17

wprime [4] représente le troisième élément (5), qui est de 4 octets depuis le début du tableau. De même l'expression wprime [6] représente le quatrième élément (7) et wprime [10] représente le sixième élément (13).

L'offset exigé pour avoir accès à un élément de tableau est calculé avec la formule suivante: nth element of array = array[(n-1) * size of element]
(Le énième élément de tableau = range [(n-1) * par la taille de l'élément])

Un String est aussi un tableau de caractères qui prend 1 octet de mémoire.

STRUCTURES

Les structures sont simplement des modèles pour des données à être stocker en mémoire d'une façon organisée. Les structures ne sont pas des données.

Une structure est un groupe de types de données abstraits qui peuvent être accessible comme une unité ou par n'importe lequel de ses champs de composants. Les champs dans la structure peuvent être des types de données différentes et de tailles différentes. Ils sont placés séquentiellement dans la mémoire quand un cas de la structure est défini dans un programme.

Vous pouvez utiliser la variable de la structure entière ou juste les champs individuels comme des opérandes dans des déclarations d'assembleur. En utilisant des structures, on doit déclarer le type de structure d'abord. Quand vous déclarez une structure ou un type d'union, vous créez un modèle pour des données. Le modèle expose les tailles et, facultativement, les valeurs initiales dans la structure, mais n'alloue aucune mémoire.

Une fois que vous avez déclaré un type de structure, vous pouvez définir n'importe quel nombre de variables de ce type. Pour chaque variable définie, la mémoire est allouée. Les champs individuels peuvent alors faire référence directement ou indirectement. Comme d'autres variables, les variables de structure peuvent avoir un accès du nom. Vous pouvez directement avoir accès aux champs dans des variables de structure avec cette syntaxe : variable.field

1. Déclarez d'abord le modèle de structure. C'est fait d'habitude avec des déclarations de prototype après les déclarations avec include et includelib :

DATE STRUCT
day BYTE ?
month BYTE ?
year WORD ?
DATE ENDS

2. Définissez ensuite la variable (un cas de la structure de DATE appelée "yesterday") l'un ou l'autre avec des données non initialisées :

.data?
yesterday DATE <>

Ou avec des données initialisées :

.data
yesterday DATE <19, 06, 1993>

3. Finalement vous pouvez avoir accès à la structure et ses champs directement du nom comme cela :

mov al, yesterday.month

Ou indirectement via un indicateur comme cela :

mov ebx, offset yesterday ;Adresse de chargement de la variable yesterday dans ebx
mov al, [ebx].DATE.month ;Copie du champ month d'ebx dans Al

L'utilisation d'un indicateur DWORD sur une structure accélère des appels de fonction puisque les grandes quantités de données dans une structure peuvent être appelées par la fonction mais seulement l'indicateur doit être mis dans la pile.

Les structures peuvent aussi être emboîtées. Les éléments de structures emboîtées ont accès de la même façon comme indiqué ci-dessus. Un exemple du format de PE est l'en-tête PE:

Ceci contient 3 éléments, tout d'abord DWORD dont la tenue de la signature de l'exécutable est non initialisé (pour PE, NE, OS/2, etc..) alors que les 2 autres sont emboîtés plus loin dans des structures tenant l'en-tête de fichier et l'en-tête facultatif. L'en-tête de fichier est comme suit :

Ainsi la Signature et les champs NumberOfSections peuvent avoir accès indirectement tout à fait simplement. Nous avons un indicateur sur le début de l'en-tête PE dans edi, alors vous pouvez super imposer le modèle de structure sur ce bloc de mémoire pour adresser les champs divers de nom comme cela :

mov eax,[edi].IMAGE_NT_HEADERS.Signature
mov bx,[edi].IMAGE_NT_HEADERS.FileHeader.NumberOfSections

Masm le comprendra aussi si les parenthèses carrées incluent l'opérande entier :

mov eax,[edi.IMAGE_NT_HEADERS.Signature]
mov bx,[edi.IMAGE_NT_HEADERS.FileHeader.NumberOfSections]

Nous en verrons plus dans le Tutorial suivant. Il y a aussi une autre façon d'utiliser des structures indirectement avec une directive qui assume. Cela dit, à l'assembleur de substituer le registre de votre choix pour une expression contenant votre indicateur de structure. Par exemple :

assume edi:ptr IMAGE_NT_HEADERS
mov eax,[edi].Signature
mov bx,[edi].FileHeader.NumberOfSections
assume edi:nothing

Cependant pendant que cela apparaît légèrement plus rapide, en assumant un registre oubliant alors de non supposer qu'il produit des résultats imprévisibles. Aussi la directive qui assume, pourrait être jusqu'ici une ligne qui a besoin que la signification soit laissée peu claire.

Il y a une quatrième syntaxe pour le faire mais c'est maladroit et ne marchera pas dans certaines circonstances (eg après une directive .if) donc il est recommandé d'utiliser la première méthode. Il utilise l'opérateur ptr et je l'ai inclus ici pour la perfection :

mov eax,(IMAGE_NT_HEADERS ptr [edi]).Signature
mov bx,(IMAGE_NT_HEADERS ptr [eax]).FileHeader.NumberOfSections

Cette section deviendra particulièrement importante quand nous parlons du codage pour des fichiers de PE plus tard.

UNIONS

Les unions arrivent aussi dans les en-têtes PE. Ils sont identiques aux structures, sauf que les champs d'une union se chevauchent dans la mémoire, qui admet que vous formatiez pour définir des données différentes pour le même espace de mémoire. Les unions peuvent stocker les types différents de données selon la situation. Ils peuvent aussi stocker des données comme une donnée type et le récupère comme un autre type de données.

Tandis que chaque champ dans une structure a un offset quant au premier octet de la structure, tous les champs dans un début d'union à le même offset. La taille d'une structure est la somme de ses composants; la taille d'une union est la longueur du champ le plus long. Un exemple d'une union dans le format de PE est le premier membre de la structure de IMAGE_IMPORT_DESCRIPTOR :

Le premier membre peut ainsi contenir des Caractéristiques ou des données OriginalFirstThunk. Dans ce cas les Caractéristiques peuvent une fois avoir été un jeu de flags mais cela a été cessé par M$ et il représente maintenant toujours les données OriginalFirstThunk.

À propos, rendez-vous compte que l'on déclare déjà la plupart des structures nécessaires pour coder dans des fenêtres (incluant PE stuff)) pour nous dans le fichier de windows.inc.


ARTeam Tutorial

Chargeur de Base(Loader)

Nouveaux Concepts:

Processus et Threads
Gestion de Mémoire Virtuelle

Nouvelles fonctions API:

CreateProcess
WriteProcessMemory
ResumeThread
TerminateProcess

Un chargeur(loader) est un programme autonome qui a pour habitude de charger et exécuter un autre programme. Il est utilisé quand nous voulons patcher une cible dans la mémoire, par exemple après qu'un contrôle de CRC soit fait, ou quand nous voulons changer quelque chose et plus tard dans le programme rétablir les octets originaux. Un exemple d'un chargeur qui est un "trainer" pour pouvoir tricher dans certains jeux.

Dans ce Tutorial nous coderons un chargeur(loader) très simple et comparerons son action au patcher que nous avons fait dans le dernier Tutorial. D'abord nous devons recourir à une certaine théorie générale sur des processus, des Threads et la gestion de mémoire.

Processus et Threads

Un processus fournit les ressources dont à besoin un programme pour être exécuter. Chaque processus est une collection d'adresse virtuelle (la mémoire), un espace du code de l'exécutable, des données, des handles ouverts aux objets du système, un contexte de sécurité, un identificateur de processus unique, des variables d'environnement, une priorité de base, le minimum et le maximum travaillant des jeux de tailles et au moins un Thread d'exécution.

Un Thread est le code qui doit être exécuté dans un processus. Un processeur exécute des Threads, pas des processus, donc chaque application a au moins un processus et un processus a toujours une direction de Thread, connu comme le Thread principal(ou primaire). Un processus peut aussi avoir des Threads multiples.

Tous les Threads d'un processus partagent son adresse virtuelle, des espaces de variables globales et des ressources. De plus, chaque Thread maintient des handles d'exception, une priorité de planification, le Thread de stockage local (eg des variables locales), un identificateur de Thread unique et un jeu de structures que le système utilisera pour sauver le contexte du Thread jusqu'à ce qu'il soit prévu. Le contexte du Thread inclut le jeu du Thread de registres de la machine, la pile du kernel, un bloc d'environnement de Thread et une pile d'utilisateur dans l'adresse espace du processus du Thread.

L'utilisation de Threads donnent leur capacité de multi gestion apparente à Windows - la capacité d'exécuter des programmes multiples concurremment. Au lieu de continuer pour exécuter un seul morceau de code jusqu'à ce qu'il soit complété, windows peut décider d'interrompre un Thread fonctionnant à n'importe quel moment et le commuter à un autre Thread.

Un Thread ressemble à un petit processeur virtuel qui a son contexte propre et sa pile propre. Les commutateurs de processeurs physiques réels entre des processeurs virtuels multiples, commencent toujours l'exécution des informations de contexte actuelles du Threads et l'utilisation de la pile du Thread.

Les composants qui gèrent des Threads dans Windows sont le planificateur et le dispatcher, qui sont ensembles responsables à se décider auquel combien de temps le Thread obtient pour poser sa condition et pour exécuter le contexte réel qui commute son temps pour changer de Threads.

Les Threads peuvent céder à l'UC de leur propre volonté, par exemple s'ils sont inoccupés en attendant que l'utilisateur saisisse. Cependant l'OS utilise un système appelé, par préemption, planification dans le cas où un Thread commence une longue tâche qui occuperait l'UC pour quelque temps. On donne un temps limité aux Thread (le quantum ou la tranche de temps) pour fonctionner avant qu'ils ne soient interrompus.

Tandis qu'un Thread fonctionne, l'OS utilise un minuteur de matériel à bas niveau interrompant pour contrôler combien de temps il a fonctionné. Une fois que le quantum du Thread a atteint son but, il est temporairement interrompu et le système permet à d'autres Threads de fonctionner. Si aucun autre Thread n'a besoin de l'UC, le Thread est immédiatement repris. Le processus de suspension et la reprise du Thread est complètement transparent au Thread. Le kernel stocke l'état de tous les registres de l'UC dans une structure de CONTEXTE avant la suspension du Thread et rétablit cet état quand le Thread est repris. De ce fait, le Thread crois qu'il n'a jamais été interrompu.

L'Ordre d'Initialisation du Processus

Il est important d'avoir une bonne compréhension de ce qui arrive quand un processus est créé. La chose suivante fournit une description brève des étapes pris par le système dans le processus moyen de l'ordre d'initialisation.

1. L'API CreateProcess crée un nouvel objet de processus et alloue une adresse espace virtuel (voir le paragraphe suivant).
 2. CreateProcess dresse alors le plan de NTDLL.DLL et le programme exécutable (le fichier .exe) dans l'adresse espace nouvellement alloué.
 3. CreateProcess crée le premier thread du processus et alloue une adresse espace de pile.
 4. Le premier thread du processus commence à fonctionner avec la fonction LdrpInitialize à l'intérieur de NTDLL.DLL.
 5. LdrpInitialize traverse récursivement les tables d'importation de l'exécutable et des plans dans la mémoire qui sont éxigés pour chaque exécutable.
 6. À ce point, LdrpRunInitializeRoutines est appelé et est responsable d'initialiser toutes les DLLS statiquement, lié actuellement et chargées dans l'espace d'adresse. Le processus d'initialisation consiste à en appeler le point d'entrée de chaque DLL avec le DLL_PROCESS_ATTACH constant voir le Code Squelettique d'une DLL.
).
7. Une fois que toutes les DLL sont initialisées, LdrpInitialize appelle la routine d'initialisation réelle du thread, qui est la fonction de BaseProcessStart de KERNEL32. DLL. Cette fonction appelle à son tour le point d'entrée WinMain de l'exécutable, auquel le point du processus a complété son ordre d'initialisation.

Fenêtres de Gestion Mémoire Virtuelle

Chaque processus a son espace d'adresse virtuel propre qui permet l'adressage jusqu'à 4 Gb de mémoire. Tous les threads d'un processus peuvent avoir accès à leur espace d'adresse virtuel, mais pas à la mémoire qui appartient à un autre processus, qui protège un processus d'être corrompu par un autre.

Des utilisations d'adresses virtuelles par un processus ne représentent pas l'emplacement physique réel d'un objet dans la mémoire. Au lieu de cela le système garde un plan de page pour chaque processus - une structure de données interne a pour habitude de traduire des adresses virtuelles dans la transmission d'adresses physiques. Chaque fois un thread fait référence à une adresse, le système traduit l'adresse virtuelle à une adresse physique. Les pages de l'adresse espace virtuel d'un processus peuvent être dans un des états suivants:

Free - La page est ni remise, ni réservée. La page n'est pas accessible au processus. Elle est disponible d'être remise, réservée, ou réservée simultanément et remise. La tentative de lire ou d'écrire dans une page libre(free) aboutit à une exception de violation d'accès.

 Reserved - La page a été réservée pour l'utilisation future. La gamme d'adresses ne peut pas être utilisée par d'autres fonctions d'assignation. La page n'est pas accessible et il n'y a aucun stockage physique associé. Elle est disponible d'être remise.

 Committed - Le stockage physique est alloué pour la page et l'accès est contrôlé par une option de protection de mémoire. Le système initialise et charge chaque page engagée dans la mémoire physique seulement pendant la première tentative de lecture ou d'écriture à cette page. Quand le processus se termine, le système sort le stockage pour des pages engagées.

La fonction de l'API VirtualAlloc est utilisée pour réserver et remettre en mémoire et spécifier des autorisations d'accès de mémoire pour des pages engagées :

Un processus utilise d'abord VirtualAlloc pour réserver un bloc de pages dans son adresse espace. Plus tard, VirtualAlloc est appelé de nouveau chaque fois qu'il est nécessaire de remettre une page de cette région réservée. La remise de la région entière au lieu de seulement le réserver consomme pour rien du stockage physique qui n'est pas nécessaire car le rendant indisponible pour l'utilisation par d'autres processus.

VirtualAllocEx est une fonction importante qui peut réserver ou remettre une région en mémoire dans l'adresse espace virtuel d'un autre processus indiqué.

Considérations en écrivant un Chargeur(Loader)

Un chargeur(loader) doit créer un nouveau processus et charger la cible, mais nous voulons faire une pause sur le thread aussitôt qu'il soit créé, ainsi nous pouvons changer les choses que nous voulons. Nous utilisons l'API CreateProcess pour cela avec la valeur de CREATE_SUSPENDED dans dwCreationFlags.

CreateProcess prend des indicateurs sur 2 structures importantes; ProcessInformation et StartupInfo. La structure de ProcessInformation est remplie du handle du processus, du handle de thread et de processus/thread ID (ouvrir win32.hlp pour plus de renseignements).

NOTE: Utilisant le handle de processus, ça vous donne l'accès avec PROCESS_ALL_ACCESS (lit/écrit l'accès) au processus entier. En utilisant le handle de thread, ça pourra vous permettre d'écrire l'accès manuellement.

Quand la cible est chargée dans le mode suspendu nous pouvons laisser le thread exécuté avec ResumeThread et mettre en pause de nouveau avec SuspendThread. Ces API prennent le handle de hThread trouvé dans le LPPROCESS_INFORMATION struct. Finalement nous pouvons lire et écrire dans le processus utilisant ReadProcessMemory et WriteProcessMemory.

Notre chargeur(loader) créera simplement un processus et chargera le thread principal suspendu de SuperCleaner. Alors il patchera le fichier dans la mémoire, puis l'exécution de résumé du thread principal et quittera.

Lancez WinAsm comme d'habitude et le coller dans loader1.asm. Bien que ce chargeur(loader) n'ait pas de GUI comme tel, il est agréable si cela a au moins obtenu une icône. Ajoutez un script de ressource au projet et ajoutez juste une icône. Cela produit le script suivant :

Le code d'assembleur est court, mais bon. Notez que puisqu'il n'y a aucun GUI (aucun appel à DialogBoxParam) nous ne devons pas déranger l'obtention et la sauvegarde du handle de module. Aussi s'il y a une erreur nous devons terminer le processus cible suspendu avant la sortie :

Une critique possible de ce code est que nous avons spécifié le VA à être patché. Cela compte pour la cible étant chargée de préférence avec ImageBase dans le patch pour travailler. Une voie plus robuste serait de spécifier que le RVA obtient alors l'adresse de base de la cible et les ajoute pour faire le VA patché. Pour cette voie, si la cible a été chargée à une base de différentes adresses, le chargeur patcherait toujours les octets correctement, bien que cela s'applique évidemment principalement aux DLL. Une autre critique est que c'est un chargeur aveugle - il ne vérifie pas, mais nous discuterons un peu plus de ces concepts avancés dans le Tutorial suivant.


ARTeam Tutorial

En savoir plus sur les Chargeurs

Nouveaux Concepts:

Utilisation du thread context
Permissions aux Accès Mémoire
Débuggeur Loaders

Nouvelles fonctions API:

SusupendThread
GetThreadContext
SetThreadContext
VirtualProtectEx
IsBadStringPtr
IsBadCodePtr
IsBadReadPtr
IsBadWritePtr
VirtualQueryEx
WaitForInputIdle
EnumDesktopWindows
GetClassName
GetWindowText
OpenProcess
DebugActiveProcess
WaitForDebugEvent
DebugActiveProcessStop

Structure du thread CONTEXT

Comme nous l'avons vu dans le dernier tutorial, chaque thread maintient une structure de CONTEXTE qui inclut les registres, la pile(stack) Kernel, un bloc d'environnement thread la pile User. Nous pouvons lire et changer les valeurs de registre d'un thread fonctionnant dans un processus en utilisant les APIs GetThreadContext et SetThreadContext pour lire ou écrire dans la structure de CONTEXTE..

Cette structure data a des champs complexes et seulement certains sont utiles mais nous pouvons spécifier le champ par get/set ContextFlags. La valeur de ContextFlags spécifie quelles parties du contexte thread est recouvré. La documentation dessus est dans WINNT.H et non à l'intérieur de MSDN :

context.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS.

L'essai get/set dans un contexte pour un thread executé donne des résultats imprévisibles. Utilisez la fonction de SuspendThread pour suspendre leThread.

Gestion d'Autorisations d'accès à la Mémoire

Une question importante en écrivant des loaders c'est qu'il doit avoir la permission à la lecture /écriture en mémoire. D'habitude en ayant accès aux sections exécutables d'un processus, ces sections sont déjà lisibles à l'affichage. Cependant si vous essayez d'écrire dans d'autres sections elles devraient alors seulement être lues, comme pour les ressources du programme, vous échouerez.

L'écriture d'un loader(chargeur) pour avoir accès aux pages en mémoire protégées implique ainsi la modification des autorisations d'accès de l'adresse patchée à PAGE_EXECUTE_READWRITE, pour la lecture et l'écriture du tout nous avons besoin de reconstituer la protection précédente. La fonction VirtualProtectEx nous permet de le faire. Les paramètres suivants ont de l'intérêt :

hProcess - Handle à processus cible - Doit avoir PROCESS_VM_OPERATION access mais habituellement l'API CreateProcess le fait pour nous.

lpAddress - Pointeur de l'adresse de base de la région des pages dont les autorisations d'accès doivent être changées. Toutes les pages dans la région indiquée doivent être dans la même région réservée allouée en l'appelant par VirtualAlloc ou la fonction VirtualAllocEx utilisant MEM_RESERVE. Les pages ne peuvent pas recouvrir les régions réservées adjacentes qui ont été allouées par des appels séparés à VirtualAlloc ou VirtualAllocEx utilisant MEM_RESERVE.

dwSize - La taille dans les octets de ce que nous voulons Patcher. Inclut toutes les pages Contenant ou plus d'octets dans la gamme du paramètre lpAddress à (lpAddress+dwSize). Cela signifie qu'une gamme de 2 octets évitant la limite de page provoquent aux attributs de protection des deux pages leurs changements.

flNewProtect - protection Mémoire - nous avons besoin de PAGE_EXECUTE_READWRITE.

LpflOldProtect - Pointe sur une variable qui reçoit la protection d'accès précédente de la première page dans la région indiquée de pages. Si ce paramètre est NUL ou ne pointe pas à une variable valable, la fonction échoue. Rappelez-vous que l'API échoue si vous mettez ce paramètre à NUL, donc nous aurons besoin d'une variable pour stocker cette valeur.

Le jeu d'API utilisées pour évaluer des droits en mémoire(évaluant si une adresse en mémoire pointée peut être lue ou écrit) inclut IsBadStringPtr, IsBadCodePtr, IsBadReadPtr, IsBadWritePtr. Tous sont semblables dans leur utilisation; juste avant l'utilisation ReadProcessMemory ou WriteProcessMemory utilise la fonction appropriée pour évaluer alors l'utilisation VirtualProtectEx pour placer si nécessaire.

Arrête le Pointer et Décide quand Patcher.

Dans des cas simples (comme dans le tutorial précèdent) nous pouvons être capables de patcher une cible dans la mémoire aussitôt que son processus est créé, mais avant les thread principales soient exécutés. Dans d'autres cas nous devons laisser la cible exécutée et l'arrêter à un point spécifique (eg après qu'une application packée a été unpackée en mémoire ou après qu'un contrôle(check CRC) a été fait. Notre loader devrait donc être capable d'arrêter la l'application à un point indiqué, de patcher le code et laisser ensuite l'application librement.
Les questions concernant la synchronisation incluent :

1. L'Attente jusqu'à arriver à l'emplacement en mémoire que vous voulez patcher est unpacké et non par la protection du packeur .

2. Le patching de l'emplacement mémoire s'effectue seulement après que le (check control) contrôle d'intégrité soit effectué.
3. Le patching de l'emplacement mémoire se fait avant que les instructions ne soient exécutées ou utilisées par la cible(application).

Il existe plusieurs façons (certaines plus puissantes que d'autres) d'arrêter l'inclusion cible :

1. L'utilisation de l'api sleep est pour l'attendre d'une durée d'ensemble pour une application packée et d'être unpackée en mémoire.

2. L'utilisation de l'API WaitForInputIdle est pour déterminer quand la cible a été exécutée, et a fini de s'initialiser et inoccupée (attend jusqu'à ce que le processus pointé attende la saisie de l'utilisateur en suspens de saisie, ou avant que l'intervalle de temps mort se soit écoulé).

3. La découverte quand la fenêtre principale de l'application a été montrée. Dans ce cas nous supposons que si la fenêtre de la l'application principale est déjà sur le bureau (visible ou pas) alors la l'application fonctionne et est prête à être patchée. Cela s'exécute en utilisant l'API EnumDesktopWindows. Cette fonction énumère toutes les fenêtres associées au bureau pointé. Il passe le handle de chaque fenêtre ouverte à une fonction de rappel de service définie par l'utilisateur. Autrement dit il invoque à plusieurs reprises notre fonction de rappel de service jusqu'à ce que la dernière fenêtre au plus haut niveau soit énumérée ou les retours de fonction de rappel de service False. Notre fonction de rappel de service(callback) appellerait simplement GetClassName et GetWindowText pour chaque fenêtre pour voir si le type de la fenêtre et le titre correspondent à celui de notre cible. La classe de fenêtre principale de la cible peut être trouvée facilement en utilisant n'importe lequel de plusieurs outils d'inspection de fenêtres comme Winspector.

4. L'utilisation de l'yAtEs trompe pour injecter un point d'arrêt dans le code à un emplacement désiré (qui doit d'abord être trouvé en mettant au point l'application). Cela implique la lecture et le stockage de 2 octets à notre point d'arrêt désiré, alors l'injection des 2 octets hexa EB FE (jmp-2) au même point d'interrompre l'exécution en créant une boucle sans fin. Nous devons alors continuellement choisir le contexte du tread (utilisation GetThreadContext) pour la valeur dans le registre d'EIP . EIP est le pointeur d'instruction et tient l'adresse de l'instruction suivante qui va être exécutée. Quand la valeur ici == notre arrêt adresse nous pouvons suspendre thread et faire notre pacthing. Une fois que nous avons fini de patcher nous reconstituons 2 octets recopiés par EBFE et reprenons l'exécution. Cela a l'avantage qu'il agisse comme "un point de contrôle", mais sans utiliser aucune API dans le débuggeur et donc sans avoir besoin de s'inquiéter de n'importe quel mécanisme d'anti-debug dans notre application. Évidemment il y a quelques suppositions avec un loader si simple :

• La victime est un simple thread
• Une fois que l'application est unpackée en mémoire il n'y a aucun contrôle d'intégrité(check control)
• La mémoire du processus cible peut être écrite
• le contexte de sécurité de la victime nous permet de procéder
• La victime n'a pas de complexe de protections anti-tampering

Débuggeur Loaders

Les chargeurs(loaders) standard décrits ci-dessus ont le grand avantage qu'ils sont simples et pas soumis aux tours de programme anti-degug, mais il y a de temps où c'est nécessaire pour le loader de déboguer l'application cible pour trouver un point d'arrêt. Un exemple typique c'est en faisant un loader pour Asprotect. Nous sommes probablement tous familiers avec l'utilisation de la DERNIÈRE méthode d'EXCEPTION pour parvenir à l'OEP mais comment faire si nous voulons que notre loader fasse la même chose ?

Dans ce cas nous utilisons l'API debug pour créer un loader pour le programme dans le débuggeur conduit d'événement simple qui peut répondre pour le déboguage. Les événements de déboguage incluent des exceptions rejetées par le packeur de la même manière nous les voyons dans Olly donc notre loader de programme de déboguage utilise en réalité SEH (le traitement d'exception structuré).

Je décrirai seulement et brièvement ce type de loader mais une description détaillée peut être trouvée dans la série de loader d'articles en cracking par Shub et TunderPwr.

De même qu'avec un loader standard nous essayons de constater qu'en un temps approprié il patch notre cible. Dans ce cas cependant notre point d'arrêt sera une exception produite par le packeur de la cible. Nous devons d'abord déboguer la cible (avec le jeu d'Olly pour ne PAS manipuler n'importe quelles exceptions). Quand nous arrivons à l'endroit correct (eg la dernière exception dans Asprotect) nous devons examiner l'exception aux caractéristiques uniques (eg l'instruction PUSH 0C ). Notre loader doit donc vérifier chaque exception que la cible produit jusqu'à ce qu'elle trouve le modèle unique.

Un loader de programme pour débuggeur peut créer un nouveau processus de debogging ou "attacher" à un existant avant l'attente des événements de mise au point. Dans le premier cas l'API CreateProcess est utilisée avec DEBUG_ONLY_THIS_PROCESS ou avec DEBUG_PROCESS (mettra aussi au point tous les processus d'enfant) dans dwCreationFlags.

Dans le deuxième cas OpenProcess est utilisé pour obtenir le PID ou l'identificateur de processus d'un processus existant et DebugActiveProcess est utilisé avec ce PID pour attacher le programme de mise au point au processus. Typiquement les programmes de mise au point ouvrent un processus avec le PROCESS_VM_READ et les flag de PROCESS_VM_WRITE pour lire et écrire en mémoire virtuelle du processus en utilisant ReadProcessMemory et WriteProcessMemory.

Le loader doit alors utiliser une boucle appropriée avec WaitForDebugEvent pour attendre la mise au point des événements de la cible. Chaque exception produite par la cible et a passé à notre loader ,le programme de mise au point par l'OS doit être examiné pour voir s'il correspond aux caractéristiques uniques, nous cherchons avant le parching en mémoire en utilisant WriteProcessMemory.

Finalement le loader peut ou pas passer le contrôle à la cible (le maintien d'attaché), détacher de la cible et le laisser(quitter) pour exécuter(diriger) librement ou fermer la cible. Le détachement semble la façon la plus propre et est accomplie avec DebugActiveProcessStop mais ce n'est pas disponible dans OS avant Windows XP.

La mémoire évaluant l'approche basée sur isBad*Ptr nous avons parlé des fonctions dans les derniers tutoriaux où le loader lance le processus cible par l'API CreateProcess. Cependant si vous avez utilisé OpenProcess avec déjà le processus de direction, vous devriez utiliser VirtualQueryEx au lieu de cela qui fournit des informations sur une gamme de pages dans l'espace d'adresse virtuel d'un processus indiqué.

Bien que l'addition de la mise au point de capacités apporte la flexibilité il ajoute aussi la complexité et la nécessité d'éviter les mécanismes d'anti-debuging divers utilisés par des protecteurs.

Comme exemple pratique nous packerons le soft SuperCleaner et essayerons notre loader du dernier tutorial. Pour faire dans la simplicité nous utiliserons upx v1.25. Commandline habituel "upx SuperCleaner.exe" produit une erreur, mais en tapant "upx SuperCleaner.exe - force" le travail :

Remarquez que notre cible a diminué de taille de 508Ko à 209Ko. Maintenant clairement notre patchers précédent ne marchera pas sur la cible packée et aucun loader ne le fera. Essayez le loader pour voir ce qui ce passe :

Notre chargeur(loader) crée le processus dans le mode suspendu et essaye immédiatement de patcher la mémoire mais l'adresse correcte n'existe pas encore comme la souche upx n'a pas décompressé la cible dans la mémoire. Quand le thread est repris, la cible est unpackée et exécutée mais notre patch n'a pas été appliqué ainsi nous obtenons le petit (le Nag).

Pour le résoudre nous coderons un loader utilisant une des techniques appropriées. L'attente de la saisie se faisant attendre ou la fenêtre principale n'évitera pas évidemment le Nag et essayant de supposer qu'un Sleep time sera trop imprécis donc nous utiliserons la méthode yAtEs.

Ouvrez d'abord la cible packée dans Olly et trouvez le saut vers l'OEP. Dans upx a packé des applications c'est un point idéal auquel il dépose ou patch l'application puisque nous ne devons nous inquiéter d'aucun CRC ou des contrôles(check) anti-tampering et la découverte de l'OEP est très facile. Nous faisons simplement défiler en bas à la fin du code pour trouver l'instruction POPAD suivie par un JMP vers L'OEP :

Dans mon cas l'OEP est 0042AADA comme vous pouvez le voir.Nous savons déjà quelle est l'adresse virtuelle à être patchée c'est 0042374F. Une bonne façon de le défaire serait d'utiliser la méthode de l'yAtEs's. Nous créons le processus dans le mode suspendu et remplaçons premiers 2 octets du saut à l'OEP (E9h E9h) par EBH FEh. Notez qu'en s'arrêtant à l'OEP lui-même n'est pas une option comme cela n'aura pas été encore unpacké dans la mémoire. Nous devons alors faire notre patching dans la mémoire, rétablissons le saut final vers OEP et reprenons le Thread. Nous améliorerons également notre loader par la vérification les bytes corrects à l'OEP et à l'endroit patché afin de vérifier la version correcte de dossier de cible.

Créez un nouveau projet dans WinASM et collez dans loader2.asm et ajoutez loader.rc de la section source :

Ce chargeur commence le processus pour notre cible et lit 2 bytes à partir de l'adresse que nous pensons être le saut vers l'OEP. Si les bytes sont corrects (dossier de cible correct), il les recouvre avec le code d'arrêt d'EBFE avant de reprendre le Thread primaire et la pause pendant 10 millisecondes pour permettre à la cible de fonctionner. La cible s'exécutera jusqu'à ce qu'elle atteigne finalement l'instruction d'EBFE qui la coincera dans une boucle sans fin et EIP ne changera pas. La souche d'upx a maintenant unpacké l'APP en mémoire et au courant du saut vers l'OEP.

Ensuite nous préchargeons le champ du flag de notre structure de CONTEXTE vide avec CONTEXT_FULL pour permettre à GetThreadContext de lire toutes les valeurs de registre et comparons dans eax la valeur actuelle d'EIP (ThreadContext.regEip) avec l'adresse de notre point d'arrêt (c'est-à-dire si nous sommes restés fidèles au point d'arrêt encore). Si nous ne sommes pas encore au point d'arrêt nous simplement la pause pour 10 millisecondes et essayons GetThreadContext de nouveau.

Si nous sommes au point d'arrêt nous suspendons le Thread et lirons les bytes à notre adresse de patchée de la cible (qui a été maintenant décompréssée dans la mémoire) pour vérifier encore si c'est correct classons les versions. Si nous avons la version correcte que nous reconstituons le saut original vers l'OEP et écrirons en notre byte patché avant de reprendre le thread et sortir, laissant notre cible patchée s'executer correctement. Si nous avons une version incorrecte de la cible nous la rapportons et terminons le Thread suspendu avant de sortir.

Notez que le CONTEXTE struct la définition doit entrer à la data section (.data?) non initialisée autrement GetThreadContext retournera une erreur, tandis que le StartupInfo et ProcessInfo Structs ne sont pas affairés et peuvent entrer à .data ou .data ?


ARTeam Tutorial

Concevoir un fichier PE

Nouveaux Concepts:

Utilisation d'une boîte de dialogue commune pour ouvrir un Fichier

Travail avec Structures - la structure d'OpenFileName
- classement des en-têtes PE

Nouvelles fonctions API:

GetOpenFileName

Dans ce Tutorial, nous coderons une application simple qui utilise les fenêtres dialogbox communes pour ouvrir un fichier exécutable et nous dire si c'est vraiment un fichier de PE valable. Le code est basé sur le Tutorial PE 2 par Iczelion, mais avec quelques changements et la structure de SEH décompté pour simplifier un peu les choses à notre but. L'idée est ici de s'habituer au traitement de structures tant en fondant la structure d'OPENFILENAME qu'en naviguant dans les en-têtes PE.

Pour démontrer qu'un fichier est un fichier de PE valable, d'habitude il est seulement nécessaire de montrer que le fichier commence par le texte "MZ" au début de l'en-tête de DOS et que la fin de l'en-tête de DOS contient un indicateur sur l'en-tête PE qui commence par le texte "PE" suivi par 2 NULS. Ces signatures ont été commodément définies dans windows.inc:

IMAGE_DOS_SIGNATURE equ 5A4Dh ("MZ")
IMAGE_NT_SIGNATURE equ 00004550h ("PE" 00 00)

Ouvrir WinAsm puis ouvrir un nouveau projet, ensuite ajoutez une nouvelle classe .rc et disposez une boite de dialogue comme cela :

Le script validate.rc est inclus à un endroit habituel. Le bouton étiqueté de 3 points permet de trouver un fichier dans son dossier de destination et pour l'ouvrir au lieu de taper le chemin complet dans l'editbox. Collez ensuite le code validate.asm dans WinAsm :

Pour la plupart de cela, ça devrait être évident maintenant. Il y a 3 nouvelles variables dont nous devons en discuter. On déclare la structure d'OFN(OpenFileName) dans windows.inc qui est comme suit :

Comme expliqué dans le dernier chapitre, c'est juste un modèle. Pour l'utiliser dans notre code nous devons en définir un model et lui donner un nom comme une variable. Nous l'appellerons ofn, mais ne pas initialiser encore n'importe laquelle de ses valeurs, donc nous utiliserons la ligne suivante:

.data?
ofn OPENFILENAME <>

Seulement 5 champs sont importants pour nous et doivent être remplis avant l'utilisation de l'API GetOpenFileName. Ceci est fait dans la Procédure de Dialogue après le "..." et que le bouton soit cliqué (à examiner dans le fichier).

L'opérateur SIZEOF retourne la taille de l'ofn et structure la variable que nous avons définie susdit. Le champ lpstrFilter indique à la variable strFilter contenant le texte qui apparaît dans le fichier en tapant dans le menu déroulant du dialogue ouvert. Le champ lpstrFile indique au buffer TargetName où le nom et le chemin du fichier ouvert seront stockés. Le champ nMaxFile contient le nombre maximal des caractères du nom de fichier et du chemin (la taille Maximum du buffer).

GetOpenFileName remplit alors le buffer TargetName avec le nom et le chemin du fichier choisi dans le dialogue ouvert. Si c'est un succès, on montre le nom dans la boîte d'édition et le buffer est vidé pour la prochaine fois avec la fonction RtlZeroMemory.

En cliquant sur le Bouton GO! , le nom du fichier est recouvert dans l'editbox, vérifié pour assurer qu'il y a en réalité eu une certaine saisie de l'utilisateur et si oui, passez à la procédure de Validation.

Le fichier cible est ouvert, un objet de configuration créé et une vue du fichier entier a dressé le plan en mémoire. Toutes ces opérations sont effectuées avec l'accès en lecture seule, indiqué ici comme c'est tout ce qui est nécessaire. Comme décrit auparavant, MapViewOfFile retourne un indicateur(pointer) dans eax au premier octet du fichier dans la mémoire.

La partie suivante illustre une façon légèrement différente d'utiliser des structures pour naviguer dans les en-têtes PE. Les structures d'en-tête sont déjà présentes dans windows.inc et cette fois-ci, nous ne devons pas définir n'importe quelles variables de structure. Pour cela nous les utiliserons comme nous les utiliserions, seulement comme des modèles pour super imposer à notre vue de fichier dans la mémoire.

D'abord nous avons besoin de l'en-tête de DOS et les seuls champs par lesquels nous nous sommes intéressés, sont les premiers et derniers membres a appelés e_magic et e_lfanew :

D'abord nous devons regarder la valeur dans e_magic. Pour l'expression, c'est bien sûr IMAGE_DOS_HEADER.e_magic pour ainsi le super imposer sur notre vue de fichier et ensuite que nous prenons l'indicateur dans eax et faisons [eax. IMAGE_DOS_HEADER.e_magic]

Si la valeur dans e_magic est égale à ce que défini IMAGE_DOS_SIGNATURE, nous devrions continuer à récupérer la valeur dans e_lfanew et l'ajouter à notre indicateur pMapping pour que cela l'indique maintenant à l'en-tête PE :

Maintenant nous sommes à l'en-tête PE et nous regardons la première "Signature" des champs pour voir si la valeur est égale à ce qui est défini dans IMAGE_NT_SIGNATURE et si oui, donc nous disons que le fichier est valable.

Le code montré utilise ici des noms de structure pour garder des choses lisibles mais vous trouverez souvent du code source où des champs divers qui ont accès en ajoutant des offsets connues dans le formulaire numérique. Par exemple, le champ e_lfanew est toujours à 3Ch des octets du début du fichier et c'est pour obtenir l'en-tête PE que vous verrez souvent ajouter eax, [eax+3Ch] au lieu d'ajouter eax,[eax.IMAGE_DOS_HEADER.e_lfanew]. Il y a d'autres exemples où cela arrive. Dans le tutorial suivant, nous nous fonderons sur ce code avec une utilité pour ajouter une section à un PE de fichier.


ARTeam Tutorial

Ajout d'une nouvelle section dans la Table des Imports

Dynamic Link Libraries (Bibliothèque de fonctions liées dynamiquement)

Nouveaux Concepts:

Utilisation fichier Mapping Augmenter la taille d'un fichier
Gestion de la Section des En-têtes et Alignement de Fichier

Nouvelles fonctions API :

lstrcpy
GetFileSize
DeleteFile

Dans ce tutorial nous nous pencherons sur le code du dernier tutorial pour ajouter une nouvelle section à un fichier PE. Cela illustrera des techniques de codage plus avancées et je recommande fortement à ce point que vous lisiez mon tutorial PE d'abord.

L'ajout d'une section est une technique utile pour savoir dans des cas où vous voulez injecter la plupart du code dans un fichier où il n'y a aucune place existante assez grande. Si vous voulez construire un outil de reconstruction d'importation personnalisé pour défaire un packeur particulier alors la solution la plus facile est d'avoir les nouvelles tables d'importation dans une nouvelle section. C'est exactement ce qu'ImpREC fait - il ajoute une nouvelle section appelée MackT du nom de l'auteur. Le schéma complet est comme suit :

1. Choisissez un fichier et vérifiez si c'est un fichier de PE valide
2. Faites un backup et mapez le fichier, Taille croissante
3. Vérifiez il y a assez de place pour un nouvel en-tête de section
4. S'il y a, calculez des valeurs pour la nouvelle section et écrivez-les dedans
5. Mettez à jour l'en-tête PE pour refléter le numéro accru de la taille du fichier et des sections
6. Décidez s'il faut garder le backup suivant de secours
7. Si aucun espace pour le nouvel en-tête de section, ne rétablissez pas le fichier original de la sauvegarde et supprimez la sauvegarde avant l'abandon.

Notes spécifiques :

1. La configuration du fichier dans avec une valeur de taille plus grand que filesize réel ajoute 00 remplissage à la fin de la vue de fichier mappé. C'est comme cela nous faisons l'espace pour notre nouvelle section sur l'extrémité du dossier. Les changements sont écrits directement au fichier sur le disque. Passez en revue le chapitre sur la Recherche et Remplace Patcher pour des détails.

2. La table de section contient une liste d'en-têtes de section, chacune 40 octets de longueur, un pour chaque section dans le fichier PE. Après l'en-tête de section final, la première section commence. Les sections sont alignées selon la valeur d'alignement de fichier (d'habitude sur des 200ème limites d'octet) avec 00 remplissage au milieu. Donc il y aura une variable somme de remplissage entre la fin de la table de section et le début de la première section . Cet espace doit être assez grand (40 octets ou plus) pour loger un nouvel en-tête de section. S'il n'y a pas assez d'espace il informera l'utilisateur et renoncera. L'espace peut être agrandi même si il est insuffisant mais cela ajoute une somme énorme de complexité et c'est au-delà de la portée de ce tutorial.

3. S'il y a l'espace nécessaire, nous pouvons y superposer notre structure d'IMAGE_SECTION_HEADER en mémoire pour écrire des données dans les champs corrects. L'ISH est comme suit :

Les champs que nous devons remplir sont Div. VirtualSize, VirtualAddress, SizeOfRawData, PointerToRawData et Caractéristiques. Name1 est facultatif et les autres peuvent être laissés pour NUL. Notez que pour adresser VirtualSize correctement vous devez le spécifier comme un membre de Div. L'alignement de ces sections est critique pour un fichier valable.

- Name1 est un Maximum de 8 octets de long et prend des caractères sans zéro terminal.
- VirtualSize est la taille dans les octets de la section que nous voulons créer (non aligné).
- VirtualAddress (VA) de nouvelle section est VA + VirtualSize de l'ancienne dernière section (doit être aligné à SectionAlignment).
- SizeOfRawData est notre taille de section désirable (aligné pour classer(déposer) l'alignement).
- PointerToRawData est compensé + SizeOfRawData de l'ancienne dernière section FileAlign

4.Après l'écriture dans notre nouvel en-tête de la section nous devons mettre à jour 2 champs dans l'en-tête PE pour assurer que le loader de windows chargera le fichier comme un exécutable valable. Le champ NumberOfSections doit clairement être augmenté de 1 et le champ SizeOfImage doit augmenté la taille de notre nouvelle section. Pour fonctionner sûrement cela doit aussi être aligné à SectionAlignment.

ouvrez WinAsm et collez le code dans (addsection.asm et addsection.rc inclus). Puisque vous êtes tous de grands garçons et filles maintenant je ne ferai pas d'analyse approfondies du code, mais j'ai mis des remarques du code source au lieu de cela, car ce que vous verrez vient de petits bouts de code d'autres personnes et d'Internet.
Le script de ressource est essentiellement le même que celui du dernier tutorial.

La partie principale de l'application devrait ressembler à ça :

La procédure de dialogue est presque la même que celle du dernier tutorial :

La procédure de validation a changé un peu pour retourner un code d'erreur à l'interlocuteur :

Et finalement la plus longue est la procédure AddSection (Ajout d'une section) :

Il serait assez facile de prendre ce code en ajout à la construction ,un outil ajoutant section qui laisse l'utilisateur saisir des informations de section exigées. Le rapport d'erreur est très dispersé et SEH pourrait être mis en oeuvre pour l'améliorer. Finalement le code pourrait être décompilé au strict minimum pour un outil de packing ou unpacking.

Il est important de se rendre compte qu'il y a beaucoup de façons de faire la même chose qui variera dans leur efficacité . Par exemple nous pourrions avoir défini une structure de PEINFO pour tenir tous nos renseignements exigés du fichier cible pour la récupération facile. Nous pourrions aussi avoir défini un IMAGE_SECTION_HEADER structurent la variable et l'a rempli de valeurs calculé comme nous sommes allés écrire la longueur dans le fichier .

De temps en temps vous serez face à un fichier binaire cible dans lequel il n'y aura pas assez d'espace à la fin de la table des sections pour ajouter un nouvel en-tête de section. Dans ce cas il est possible d'agrandir le fichier binaire et créer l'espace entre la fin de la table de section et le début de la première section pour un nouvel en-tête. Cependant c'est plus complexe ,et demande l'implication de mettre à jour les positions de toutes les sections existantes dans la table de section.

Il est aussi possible d'agrandir la section finale d'un exécutable pour faire de l'espace pour le nouveau code. C'est relativement facile, mais parfois c'est compliqué par le fait que l'en-tête de la section finale n'est pas toujours l'entrée finale dans la table des sections donc la table des sections entière doit être examinée pour trouver celui avec le plus haut indicateur de Raw Data (PointerToRawData) avant que les champs appropriés ne puissent être modifiés.


ARTeam Tutorial

Code Squelettique d'une DLL

Nouveaux Concepts:

Bibliothèques et Jonctions
La procédure de la DLL EntryPoint
Le code squelettique pour créer des DLL
L'utilisation d'un fichier DEF pour exporter des fonctions

Les DLL ont quelques utilisations importantes dans le Reverse. Tout d'abord il est utile d'être capable d'écrire des plugins pour vos outils de reverse favoris comme ImpREC, Olly et PEID ou même votre IDE favori. Les Plugins prennent la forme d'une DLL utilisée par l'application.

L'utilisation la plus importante est l'injection d'une DLL dans laquelle une DLL peut être forcée à être chargée dans l'adresse espace d'un processus cible. Comme nous l'avons déjà vu avant, elle peut avoir accès à toute la mémoire du processus et contrôler l'application entière. Un bon exemple pour une utilisation, ce sont les articles sur l'unpacking avec des traceurs par NCR. Ceci est une structure avancée et dans ce Tutorial nous commencerons à apprendre et à comprendre comment fonctionnent les DLL et regarderons le code squelettique(Les bases du code) pour écrire cette DLL.

Les petits bouts de code ou de fonctions Fréquemment utilisés sont souvent stockés sous forme compilée à l'extérieur de l'exécutable qui les utilises dans des modules appelés des bibliothèques. Il y a 2 principaux types :

Bibliothèques Statiques

Ceux-ci sont utilisés principalement pendant le développement du programme. Dans un processus qui appelle la jonction statique, le linker(liaison) copie les fonctions exigées de la bibliothèque (qui d'habitude a l'extension .LIB) directement dans d'autres modules pour former le fichier exécutable final. Les bibliothèques, pendant l'exécution sont un bon exemple. L'inconvénient c'est qu'avec les exécutables, c'est l'utilisation des mêmes fonctions qui contiendra le code fait en double et consommera donc plus de RAM.

Bibliothèques de Liaison Dynamiques

Ceux-ci sont utilisés pendant l'exécution du programme. Dans ce cas, les fonctions de bibliothèque ne sont construites à aucun moment dans le fichier .EXE. Au lieu de cela ils résident dans un module séparé en formulaire exécutable (fichier au format PE) appelant une Bibliothèque de Liaison Dynamique. Cela contient des fonctions et des données qui peuvent être utilisées par un ou plusieurs autres modules ou "clients". Quand le premier client exige une DLL, Windows charge la DLL dans le client à l'adresse espace virtuel pour que le client puisse avoir accès aux fonctions dont il a besoin.

Les processus multiples qui chargent la même DLL à la même adresse de base partagent une seule copie de la DLL dans la mémoire physique. Le fait de faire cela, économise la mémoire du système et réduit l'échange. Tous les processus qui utilisent la même DLL partagent son code, mais ont leur copie unique propre de leur section de données.

Windows fournit deux méthodes pour dynamiquement lier une DLL; jonction implicite (une fois chargée) et jonction explicite (la durée d'exploitation). La différence se trouve avec le client; la DLL est la même dans les deux cas.

Jonction implicite(une fois chargée)

Windows charge la DLL automatiquement avec le premier programme du client et écrit les adresses de fonctions exigées dans la table d'adresse d'importation du client avant que le client ne commence l'exécution. C'est simple et n'exige aucun code supplémentaire, mais pour l'utiliser, l'application du client doit être lié avec une bibliothèque d'importation (le fichier .LIB) pour la DLL en utilisant la déclaration includelib.

Quand le module du client est construit, le linker(liaison ou lien) exécute n'importe quelle adresse pour arranger le fichier exécutable final, mais dans le cas des fonctions qui résident dans une DLL externe, le linker ne sait pas que ces adresses construisent un temps. Dans ce cas le linker utilise une bibliothèque d'importation pour la DLL (a appelé vaguement aussi le fichier .LIB). Une bibliothèque d'importation ne contient aucun code exécutable, elle nomme seulement les emplacements de fonctions exportées par la DLL. Le linker utilise les emplacements dans la bibliothèque d'importation pour résoudre des références aux fonctions de DLL externes du client.

En général, le chargement implicite est le meilleur cas quand un client exige toujours au moins une procédure dans la DLL, puisque Windows charge automatiquement la DLL du client. L'inconvénient de cette méthode est que si la DLL exigée est introuvable, Windows refusera d'exécuter votre application! Si vous chargez la DLL explicitement comme dans la méthode suivante et donc que la DLL non essentiel ne peut pas être trouvé, votre programme peut simplement informer l'utilisateur et continuer l'exécution.

Jonction explicite(la Durée d'exploitation)

Windows ne charge pas la DLL avant que le premier client ne le demande explicitement pendant l'exécution. Pour le faire, le client utilise les fonctions d'API suivantes :

LoadLibrary - Charge la DLL dans la mémoire
GetProcAddress - Obtenir l'adresse de chaque fonction exigée
FreeLibrary - Décharge la DLL quand les fonctions ne sont plus exigées

Bien que cela exige plus d'effort de programmation dans le client pour qu'il permette au contrôle que les DLL (si chacune) se chargent au temps exécuté. Un fichier de LIB pour la DLL n'est pas nécessaire bien que vous deviez toujours savoir combien de paramètres les fonctions prennent à l'intérieur de cela, etc. De plus vous pouvez appeler les fonctions non documentées qui ne sont pas détaillées dans les bibliothèques d'importation et sont souvent exportées par ordinal seulement.

Pour charger une DLL, implicitement ou explicitement, Windows cherche le fichier DLL dans les répertoires suivants dans l'ordre montré:

1. La liste d'adresses actuelle
2. La liste d'adresses de Windows, qui contient par exemple WIN.COM
3. La liste d'adresses du système de Windows, qui contient des fichiers du système comme GDI.EXE
4. La liste d'adresses où le programme du client réside
5. Répertoires inscrits dans le string d'environnement du CHEMIN
6. Répertoires qui dressent le plan d'un réseau

Si Windows ne localise pas la DLL dans n'importe lequel de ces répertoires, il avertit l'utilisateur avec une boîte de message.

Code de la DLL

Le code dans une DLL consiste à des fonctions exportées et non exportées. Les fonctions Exportées sont des routines publiques servant des clients tandis que les fonctions non exportées fournissent l'appui privé, interne pour les procédures exportées et ne sont pas visibles à un client. Les fonctions Exportées doivent être inscrites dans la section EXPORTATIONS d'une définition de module (le fichier .DEF) - un fichier texte contenant des informations utilisées par le linker. Nous en discuterons bientôt.

La Fonction DLLEntryPoint

Une DLL a d'habitude une fonction de point d'entrée comme indiqué ci-dessous (cela ne doit pas être appelé DLLENTRYPOINT, il peut être appelé autrement, comme eg. LibMain qui est généralement utilisé.) le système appelle la fonction de point d'entrée chaque fois qu'un processus ou un thread chargent ou déchargent la DLL. Il peut être utilisé pour exécuter l'initialisation simple ou des tâches de nettoyage.

Le système appelle la fonction de point d'entrée dans le contexte du processus ou du thread qui a fait que la fonction a été appelée. Cela permet à une DLL d'utiliser sa fonction de point d'entrée pour allouer la mémoire dans l'espace d'adresse virtuel du processus d'appel ou ouvrir des handles accessibles au processus. La fonction de point d'entrée peut aussi allouer la mémoire qui est privée à un nouveau thread en utilisant le thread du stockage local (TLS).

DLLENTRYPOINT typique y ressemble (voir Win32.hlp):

Autrement dit, quand Windows appelle votre DLLENTRYPOINT, il passe 3 paramètres mais le 3ème n'est pas utilisé :

hInstDLL est le handle du module de DLL. Ce n'est pas le même comme le cas de handle du processus et est utile si par exemple votre DLL a des ressources comme ceux-ci qui ont accès par le handle de DLL. Vous devriez garder cette valeur si vous deviez l'utiliser plus tard comme vous ne pouvez pas l'obtenir de nouveau facilement. La valeur ne change pas à moins que la DLL ne soit déchargée et chargée de nouveau donc elle est sûr d'être stocker dans une variable global.

fdwReason peut être une des quatre valeurs :

DLL_PROCESS_ATTACH - la DLL reçoit cette valeur quand elle est d'abord injectée dans le processus de l'adresse espace. Vous pouvez utiliser cette occasion de faire l'initialisation.

DLL_PROCESS_DETACH - la DLL reçoit cette valeur quand elle est déchargée du processus de l'adresse espace. Vous pouvez utiliser cette occasion de faire un certain nettoyage en désaffectant de la mémoire.

DLL_THREAD_ATTACH - la DLL reçoit cette valeur quand le processus crée un nouveau thread.

DLL_THREAD_DETACH - la DLL reçoit cette valeur quand un thread dans le processus est détruit.

Vous devez retourner VRAI dans eax si vous voulez que la DLL continue à fonctionner. Si vous retournez FAUX, la DLL ne sera pas chargée. Par exemple, si votre code d'initialisation doit allouer un peu de mémoire et qu'il ne peut pas le faire avec succès, la fonction d'entrypoint devrait retourner FAUX pour indiquer que la DLL ne peut pas fonctionner.

Pour des processus utilisant la jonction implicite (une fois chargée), une valeur de retour de causes FAUSSES à l'initialisation du processus le fait échouer et le processus se termine comme nous avons dit susdit. Pour des processus utilisant la jonction explicite (la durée d'exploitation), une valeur de retour de causes FAUSSES et c'est le LoadLibrary ou la fonction de LoadLibraryEx qui les font rendre un NUL, alors l'échec de l'indication et le système déchargent simplement la DLL.

D'autres Fonctions

Vous pouvez mettre vos fonctions n'importe où dans la DLL, mais si vous voulez qu'elles soient accessibles à d'autres modules, vous devez mettre leurs noms dans la liste d'exportation dans un fichier DEF comme nous avons dit susdit. Ensuite nous regarderons le code essentiel pour une DLL et son fichier DEF.

Le fichier ASM

Créez un nouveau projet dans WinAsm vide, mais choisissez cette fois le "Standard DLL":

Sauvez maintenant le projet et coller le code de DLLSKELETON.ASM dans WinAsm:

Si le DLLENTRYPOINT proc est seulement pour être appelé une fois quand la DLL est chargée, alors les conditions traitant les autres situations peuvent être omises et "vrai" peut être rendu immédiatement. Cela semble beaucoup plus simple - voir le Tutorial suivant par exemple.

Note: L'avancement soulignée pour nos noms de fonction exportés n'est pas nécessaire mais beaucoup de programmeurs l'ajoutent pour distinguer leurs fonctions propres de l'API.

Le fichier DEF

Créez ensuite un fichier texte vide dans le dossier de projet et rebaptisez-le DLLSKELETON.DEF puis dans WinAsm, cliquez sur le bouton pour Ajouter des Fichiers (voir la photo en dessous):

Double-cliquez sur le fichier de DLLskeleton.def et il devrait apparaître dans la barre d'explorateur de projet :

Maintenant WinAsm changera les options linker correctement pour l'utiliser et vous serez capables de l'éditer directement dans la fenêtre de code. Ajoutez les lignes suivantes :

LIBRARY DLLskeleton
EXPORTS _ExpFunc1 @ 01
_ExpFunc2 @ 02
_ExpFunc3 @ 03

Les mots de la BIBLIOTHÈQUE et des EXPORTATIONS sont des déclarations de définition de module. Il y a beaucoup d'entre eux détaillés dans la documentation masm61 mais ceux-ci sont les 2 seuls que nous devons connaître:

La déclaration de bibliothèque n'est pas en réalité exigée - notre projet sera toujours construit comme une DLL sans cela.

Notez s'il vous plaît le mot-clé NONAME ci-dessus pour exporter des fonctions par ordinal seulement. Nous l'utiliserons dans le Tutoriel suivant. Pour plus de renseignements sur des exportations et ordinals voir mon Tutorial PE. Notez que dans les commentaires, on inclus des fichiers qui ont la même syntaxe dans des fichiers de def que dans des fichiers asm.

Le fait de cliquer sur Go, tous les fichiers dont on a besoin vont se créer, non seulement la DLL, mais aussi un fichier LIB que vous pouvez utiliser pour avoir accès à vos fonctions par la jonction implicite (une fois chargée):

Dans le Tutorial suivant nous regarderons les différentes façons d'appeler des fonctions conservées dans une DLL.


ARTeam Tutorial

Exemple d'une DLL Simple

Nouveaux Concepts:

L'étude pour appeler des fonctions exportées par une DLL (Dynamic Link Library)

fichier contenant une bibliothèque de fonction utilisée par les programmeurs.

Nouvelles fonctions API:

LoadLibrary
GetProcAddress
FreeLibrary

Dans ce chapitre nous coderons une DLL simple avec des fonctions exportées et regarderons les façons différentes d'appeler la fonction de notre application client. Dans le premier exemple nous utiliserons la jonction implicite(une fois chargé) et dans le deuxième nous utiliserons la jonction explicite(la durée d'exploitation). Dans le deuxième cas nous importerons une des fonctions par ordinal. C'est la méthode que nous devons utiliser pour avoir accès aux API non documentées comme les noms de fonction n'apparaîtront pas dans la DLL ou de la bibliothèque d'importation disponible.

D'abord nous construirons une DLL pour exporter 2 fonctions simples - un de nom et l'autre par ordinal seulement. Créez un nouveau projet dans WinAsm et s'assurer que vous utilisez les fixations DLL dont on a déjà discutées dans le dernier Tutorial. Maintenant collez le code de MyDLL.asm dans WinAsm:

Ajoutez un fichier DEF avec les lignes suivantes:

EXPORTS

_ExpFunc1 @ 01
_ExpFunc2 @ 02 NONAME

Souvenez-vous dans le dernier Tutorial que le mot-clé NONAME arrête le nom de fonction étant placé dans la DLL et l'exporter par ordinal seulement. Cliquez Go pour créer les Fichiers MyDLL.LIB et MyDLL.DLL. Si vous le souhaitez, vous pouvez vérifier la DLL résultant avec n'importe quel PE Explorateur d'outils, par exemple CFF Explorer par Ntoskrnl:

Notez que la fonction avec la valeur ordinale 2 n'a aucune valeur dans les champs de nom et aussi que NumberOfFunctions est 1 fois plus grand que NumberOfNames.

Pour utiliser le fichier LIB pour lier une application avec notre nouvelle DLL au temps de charge, nous avons aussi besoin d'un fichier INC. Cela peut être fait avec l'utilitaire lib2inc, mais peut aussi être écrit à la main comme un fichier texte et renommer MyDLL.INC. Il contient simplement une liste des prototypes de toutes les fonctions exportées par la DLL et de la même manière nous écrivons des prototypes pour nos procédures propres. Pour utiliser lib2inc ouvrant une commande sans faute du dossier contenant le fichier lib, lib2inc et le type "lib2inc MyDLL.lib", voir l'image ci-dessous (sans citations) :

Cela créera un fichier INC simple comme décrit ci-dessus:

Maintenant nous coderons une application rapide pour lier notre DLL une fois chargée et importerons les fonctions. Lancez WinAsm et faites un nouveau projet - Standard exécutable cette fois-ci. Collez le code Implicit.asm dans WinAsm:

Cet exemple d'application appelle simplement les 2 fonctions l'une après l'autre et quitte ensuite. Notez bien que la deuxième fonction est exportée par ordinal seulement et que le nom n'est pas incorporé dans la DLL, donc nous pouvons toujours l'appeler de nom(ex: include MyDLL.inc, include MyDLL.lib, etc...) parce que nous avons les fichiers LIB et INC.

Maintenant nous changerons la susdit application pour qu'elle utilise la jonction de durée d'exploitation. La première fonction sera appelée de nom et la deuxième par ordinal. Créez un nouveau projet et puis coller le code de Explicit.asm dans WinAsm:

Dans ce cas nous utilisons LoadLibrary pour charger notre DLL dans le processus adresse de l'espace en rendant son handle, puis GetProcAddress pour trouver l'adresse des fonctions que nous voulons appeler. GetProc peut utiliser un indicateur sur le nom de la fonction ou la valeur ordinale de la fonction.

Notez que nous n'avons pas besoin d'un lib ou d'un fichier inc, mais si la fonction appelée avait eu besoin des paramètres spécifiques, nous devrions connaître ceux pour l'utiliser correctement.

Finalement FreeLibrary est utilisé pour décharger la DLL. Cela arriverait en réalité automatiquement après l'appel à ExitProcess mais c'est la bonne pratique de codage pour le faire de toute façon.


ARTeam Tutorial

Coder un Plugin pour ImpREC

Comme nous l'avons dit auparavant, plugins pour tous nos outils favoris de reverse ont tendance à se présenter sous la forme de DLLS. Dans cet exemple nous regarderons le codage pour ImpREC comme c'est assez simple. ImpREC est un outil excellent pour réparer ou reconstruire les importations d'exécutables corrompu par des packeurs. Il échoue sur certains des protecteurs plus récents, mais il restes plein de ressources comme c'est extensible via plugins. Les tutoriaux quant à son utilisation sont abondants sur le web et il est détaillé aussi dans mon tutorial sur les fichiers PE.

MackT a fourni des instructions à l'auteur d'ImpREC dans Plugin.txt dans le dossier où ImpREC est installé :

Les points importants ci-dessus sont encadrés en rouge. La chose suivante est le code source pour tELock v0.92 . Je ne l'ai pas codé moi-même et je ne sais pas qui est l'auteur . C'est un bon exemple comme c'est très simple. J'ai ajouté plus de commentaires pour expliquer chaque ligne de code. Si vous voulez faire un projet WinAsm et construire cela rappelle la description des DLL décrites dans le chapitre squelette DLL. Asm et des fichiers de Def sont dans la section de code source comme d'habitude.

Notez qu'il y doit toujours avoir une procédure DLLENTRYPOINT vide (appelé LibMain dans ce cas) et que le seul l'autre procédure est notre fonction de trace construite selon les instructions de MackT.

Le fichier DEF est aussi très simple comme d'habitude contenant seulement 2 lignes :

LIBRARY tELock
EXPORTS Trace

Coder un plugin pour OllyDbg

C'est exactement le même principe mais OllyDbg un logiciel un peu plus complexe . A PDK (plugin development kit) est disponible en téléchargement dessous:

http://www.ollydbg.de/plug110.zip

Qui inclut la documentation et 2 échantillons entièrement fonctionnels et bien faits avec des remarques. Dans la documentation :

Un Plugin est une DLL qui réside dans le répertoire d'installation d'OllyDbg et ajoute une fonctionnalité à celui-ci. Pour information,certain plugin différent exige des noms uniques.udd (udd) comme extension, nametypes etc... Pendant le démarrage, OllyDbg charge toutes les DLLS disponible une à une et cherche des points d'entrée nommés _ODBG_Plugindata et _ODBG_Plugininit. Si ces plugins sont pris en compte et compatibles ou déja présent dans le répertoire "plugin", OllyDbg enregistre ces plugins et les ajoute dans les sous-menu au menu contextuel Plugins dans le menu principal d'OllyDbg.

Les Plugins peuvent êtres ajoutés dans le menu comme Disassembler, Dump, Stack, Registers, Memory, Modules, Threads, Breakpoints, Watches, References, Windows and Run trace windows etc... Ils peuvent être considérés raccourcis globaux,comme des raccourcis d'une des fenêtres inscrites. Ils peuvent aussi créer des fenêtres MDI propres. Les Plugins peuvent écrire des données plugin-spécifiques aux fichiers de .udd avec des informations dépendantes de module et ollydbg.ini et avoir accès aux structures de données différentes qui décrivent l'application débuguée. Il y en a plusieurs en général, des fonctions de rappel de service qui permettent l'interaction facile mais proche avec OllyDbg.

Malheureusement, les deux échantillons de plugins sont écrit en C avec des instructions pour être compilée sous BCC55, BCB5 ou M$VC ++.J'ai pu avoir un exemple autour duquel j'ai vu qu'il a été codé en MASM par SV et EviLoid. Il a fait là un exemple idéal parce que c'est une réparation simple pour l'API IsDebuggerPresent qui est aussi importante pour notre compréhension. Ce n'est pas compilable cependant comme quelques fichiers d'inc manquent mais j'ai inclus l'asm et les fichiers de def dans la source ICI.


ARTeam Tutorial

Conclusion

J'espère que cela servira à beaucoup d'entre vous, et surtout qu'il motivera beaucoup de personnes à commencer à coder. Ce n'est pas vraiment si difficile que ça ,une fois qu'on s'est jeté à l'eau et cela commence à vous sembler plus clair et résout bien des problèmes. Je vous fais mes excuses si mon code vous semble un peu confus (Je n'est pas cherché à l'optimisé pour le rendre le plus lisible), mais je me considère toujours comme un néophyte. Le travail des amis et la diffusion libre que vous apprenez. si vous avez des commentaires, questions ou des corrections entrez s'il vous plaît en contacte avec moi goppit@hotmail.com

Références & Bibliographie

Le matériel de lecture le plus essentiel inclut les tutoriaux par Iczelion sur le codage de masm et le format de PE - ceux-ci ont été le point de départ pour la plupart des codeurs asm d'hier et d'aujourd'hui. Ce souci asm codant en général plutôt qu'être RCE-BASE et la plupart des exemples utilisent des applications à fenêtres complètes plutôt que dialogbox de base, que j'ai évité ici de vous mettre ici pour gagner du temps et j'ai gardé les choses simples. Les sections sur le format de PE et l'API de Mise au point en font partie.

http://web.sau.edu/LillisKevinM/csci240/masmdocs/

http://win32assembly.online.fr/

La meilleure source d'informations sur la syntaxe de MASM est le Guide de MASM 6.1 Programmeur et MASM 6.1 Référence par M$ et 1992 daté. Une collection de cette documentation est disponible ici :

http://web.sau.edu/LillisKevinM/csci240/masmdocs/

De plus,qu'Intel product à fait un tas de manuels énormes d'en environ 5 volumes détaillants le jeu d'instruction complet pour les processeurs x86. Ceux-ci sont très détaillés et probablement de peuvent servir aux débutants, et sont disponibles en téléchargement ici :

http://www.intel.com/design/pentium4/manuals/index_new.htm#1

Il y a aussi des liens sur la même page où trouver les manuels gratuitement sur CDROM ou sur papier!

Le forum Win32AsmCommunity et MASMFORUM sont la source suivante la plus de valeur d'informations :

http://win.asmcommunity.net/board/index.php

http://www.masmforum.com/simple/index.php

Le meilleur ebooks que j'ai vu sur l'assembleur est comme suit ::

Assembleur Point par point : la Programmation avec le DOS et le 2ème Rédacteur Linux - Jeff Duntemann (John Wiley et des Fils 2000)
l'Art d'Assembleur - Randall Hyde 2003)
l'Introduction à 80x86 l'Assembleur et l'Architecture Informatique - Richard C. Detmer (Jones et Bartlett Publishers 2001)
la Programmation de Fenêtres dans l'Assembleur - Randall Hyde
des Assembleurs et des Chargeurs - David Salomon (Ellis Horwood)
Linkers et Loaders - John R Levine

Le meilleur ebooks que j'ai vu sur l'architecture des APIs de Windows est :

Programming Windows 5th Ed - Charles Petzold (M$ Press 1998)
Programming Applications for M$ Windows 4th Ed - Jeffrey Richter (M$ 1999)
Windows Internals 4th Ed - Mark Russinovich & David Solomon (M$ Press 2004)
Reversing: Secrets of Reverse Engineering - Eldad Eilam (Wiley Publishing 2005)

Ceux-ci sont tous disponibles en pdf ou en format chm à de divers endroits sur le net.

Finalement je mentionnerai de nouveau la série excellente de tutoriaux d'ARTEAM sur le cracking ,sur les loaders et les articles de traceurs par NCR. Il y a aussi beaucoup de code source intéressant partagé tous deux dans les tutoriaux mentionnés, mais également sur les forums de reverse.

Salutations et Remerciements

Salutations et Remerciements à tous les membres d'ARTEAM et aux lecteurs. Grâce à tous, la communauté du reverse partage librement les codes et les informations.

je salue et remercie tout particulièrement Shub, Nilrem, Gabri3l, lena151 et bien sûre Iczelion... et tous ceux que je n'ai pas cités.

Traduction Française

Mars 2008

Nous tenons à nous excuser par avance si toutefois lors de la lecture vous avez rencontré des phrases difficiles à comprendre. Cela vient du fait que nous ne sommes pas traducteurs de métier, nous l'avons traduit pour rendre service à certains qui ont déjà beaucoup de mal à comprendre certains termes techniques du reverse. Soyez donc indulgent, nous avons fait de notre mieux. Merci.

Sincères Salutations et Remerciements à Goppit pour cet excellent Tutorial.

Merci à Goldocrack de la Team ODC de nous avoir conseillé ce tutorial

sans qui nous n'aurions jamais eu ni l'idée ni l'envie de le traduire.

@++

SPOKE3FFF & DcSpirou

Goldocrack de la Team ODC

http://xtx.free.fr/


ARTeam Tutorial

Installation de MASM32 et WinAsm Studio

Installation de MASM32

Téléchargez tout d'abord MASM32 et le pack WinAsm (les liens sont à la page d'introduction). Dézipez m32v82r.zip et exécutez le INSTALL.EXE qui vous demandera alors de spécifier le chemin d'installation. Si vous choisissez C:\ par conséquent l'adresse d'installation sera C:\masm32\. Tous les utilitaires et les programmes de même types qui viennent avec Masm32 s'attendront à ce que ce soit installé directement de la racine de la partition.

Le programme d'installation ouvrira une fenêtre de type console(MSDos) demandant pour construire les bibliothèques MASM , patientez car cela peut prendre quelques minutes :

Après à la fin de processus de construction pressez n'importe quelle touche du clavier , vous devriez obtenir une fenêtre avec le message "installation success".

Le conseil de Hutch qui a réuni le pack MASM32, est que les bibliothèques devraient toujours être construites sur votre propre system plutôt que des copies venant d'ailleurs , cela est conseillé afin d'empêcher la corruption de fichiers essentiels.

Installation de WinAsm Studio

1. Installez-le en décompressant le zip dans C:\Program Files\WinAsm Studio

2. Lancez WinAsm et allez dans Tools>Options>Files&Paths et vérifiez que tous les paths (Fichiers d'aide doivent être redirigés vers WIN32.HLP et le Chemin Projets vers le répertoire de votre code source)::

3. Un débogueur et un éditeur de ressource peuvent être configurés Tools>Options>Miscellaneous:

Quand vous aurez un projet ouvert dans WinAsm en appuyant Ctrl+F9 cela ouvrira Olly avec le projet actuel exécutable chargé, il faut que la checkbox de "Use quotes for file name"et aussi l'autre checkbox soient cochées comme sur l'image au dessus ,pour que cela fonctionne parfaitement.

4.Prenez comme référence, mes paramètres généraux:

5. Un fichier d'aide (WinAsm.chm) est disponible sur le site Web qui devra se trouver dans le répertoire C:\Program Files\WinAsm Studio\Help.

6. Le Tools Manager (sous le menu d'Outils) vous permet d'ajouter des entrées pour vos utilitaires favoris dans le menu outils. Il y a des outils très utiles à l'adresses d'installation masm32 qui valent la peine d'être ajoutés - particulièrement MNEMONIX.EXE (qui convertira n'importe quelle donnée hexadécimale opcode en son plus significatif mnémonique) et LIBLIST.EXE (qui vous dira quelle DLL et n'importe quelle fonction d'API donnée ou elle réside). J'ai aussi ajouté des entrées pour Table Extractor, RGN Creator et plusieurs fichiers d'aide.

7. Des Add-Ins Manager peuvent être ajoutés et sont disponible sur le site Web WinAsm. En particulier le Nouvel assistant de Projet est une grande amélioration sur la fonctionnalité existante. Pour utiliser un Add-in - il faut simplement copier la DLL dans le répertoire C:\Program Files\WinAsm Studio\AddIns , ouvrez Add-Ins Manager (Add-Ins menu) et configurez-le comme vous le souhaitez. Je vous recommande aussi de regarder le Quick Help System et l'Aide de H2 Context Help add-ins.

QHS ouvre un encadré contenant une définition du mot sous le curseur dans la fenêtre de l'éditeur de code. Les définitions de chaque mot sont stockées comme des fichiers de .rtf séparés dans le répertoire C:\Program Files\WinAsm Studio\Help\QHS\ donc vous pouvez facilement ajouter à ce qui est déjà là en écrivant vos propres définitions de mots.

H2 Context Help add-in est excellent si vous installez la Plate-forme SDK. Il requière le logiciel freeware H2 programmé par Rob Chandler et disponible ici : http//helpware.net/mshelp2/h2viewer.htm et montera le fichier d'aide de MSDN pour le mot sous le curseur dans WinAsm en appuyant F1. Si vous avez le vieux fichier de Win32.hlp configuré comme ci-dessus il est toujours disponible sous le menu d'aide dans WinAsm.

Diagnostic des erreurs

Si la signalisation d'erreur de syntaxe WinAsm ne fonctionne pas, alors la cause la plus probable est que les chemins d'accès sont mal configurés (voir ci-dessus).

L'erreur suivante peut arriver avec masm si le code source est sur une partition différente du dossier d'installation masm :

fatal error A1000: cannot open file : \masm32\include\windows.inc

Cela peut être évité par précédent Include et includelib dans le code source par leur chemin complet par exemple "include C:\masm32\include\windows.inc" plutôt que juste "include windows.inc". Cependant si les chemins sont configurés correctement comme ci-dessus alors WinAsm devrait donner les chemins corrects à l'assembleur et linker automatiquement

Personnalisation de votre installation MASM32

Le pack MASM32 Contient la version 6.14 de ml.exe (l'assembleur) et la version 5.12 de link.exe (le linker) parmi d'autres fichiers binaires. Le pack peut être "mis à jour" en recopiant les fichiers dans C:\masm32\bin\ avec la version plus récente 7.10 fichiers Visual Studio 2003. Ceux qui sont inclus sont les suivants:

cvtres.exe
dumpbin.exe
editbin.exe
lib.exe
link.exe
ml.exe
nmake.exe
rc.exe
msdis140.dll
msobj71.dll
mspdb71.dll
rcdll.dll

Cette version est significativement plus rapide en assemblant et liant de grands projets et est probablement la meilleure version. Des versions plus récentes sont disponibles, mais sont malheureusement infectées de l'embonpoint de .NET.

Suivant votre installation vous pouvez avoir une erreur après la mise à jour des outils v7.10 quand le linker est utilisé - "This application has failed to start because MSVCR71.dll was not found". Cela fait partie de la Bibliothèque Pendant l'exécution M$ Visual C Runtime Library et si vous n'avez pas cette DLL,elle peut être téléchargée librement sur de nombreux sites Web - Avec Google vous pouvez trouver.

PE des fichiers produits utilisant M$ des outils de développement contiennent des octets supplémentaires "garbage code" à l'origine du DOS. Ceux-ci sont insérés par le linker Link.exe à compilent le temps. Dans tous les cas, le nec plus ultra reste DWORD. Ces données ne sont pas présent dans les fichiers produits avec d'autre linkers (eg Borland, GCC, fasm, golink, polink, etc). Ce comportement est exposé par toutes les versions de M$ Link.exe de v5.12.8078 qui fait partie du pack MASM32, jusqu'à v7.10.3077 qui expédie avec dernier C Visuel ++ des packs.

Les données incluent les codes encryptés qui identifient les composants ont eu l'habitude de compiler le fichier de PE. On dit que cela a mené à la poursuite d'un virus comme il permet à M$ de prouver que le virus a été compilé sur votre PC.

Le dword après "Rich" est une clef produite par le linker qui se répète plusieurs fois dans les garbage data. Quand nous compilons un programme le compilateur met la string "@comp.id" suivi par un compilateur DWORD-TAILLE ID le numéro dans notre fichier d'obj. Quand nous nous lions notre obj classe le linker extrait le numéro de comp.id et XORS cela avec la clef et l'écrit dans "garbage" comme 2ème DWORD auparavant "Rich".

Les "@comp.id" variables sont codées en dur :
ML.EXE Ver.6.14.8444 -> comp.id is 1220FC (Vous pouvez chercher: FC2012)
ML.EXE Ver.7.00.9466 -> comp.id is 4024FA (recherche: FA2440)
ML.EXE Ver.7.10.2179 -> comp.id is 0F0883 (recherche: 83080F)
ML.EXE Ver.7.10.3077-> comp.id is 0F0C05 (recherche: 050C0F)
C++ Optimizing Compiler Version 12.00.8804 for 80x86 ->comp.id is 0B2306

Le 1er DWORD avant "Rich" est la clée XORée avec la constante 536E6144h codée en dure. Si nous cherchons "@comp.id" dans notre fichier obj et que nous substituons le DWORD Après cela avec des zéros nous verrons que le second DWORD avant "Rich" est égal à la clé (DWORD après "Rich") Relisez si ce n'est pas clair du 1er coup en regardant en même temps l'image du dessous.

Voici en exemple un simple "hello world" le type de programme codé dans MASM32 et ouvert dans l'éditeur hexadécimal. Les octets supplémentaires sont encadrés en rouge :

Heureusement il est possible de patcher le linker pour arrêter ce comportement. Il y a un utilitaire appelé SignFinder.exe par Astérix (inclus Ici) Qui vous permet de trouver rapidement le code qui a besoin du patch dans n'importe quelle version de Link.exe. Utilisation v5.12.8078 de MASM32 comme un exemple :

Ouvrez donc Link.exe dans Olly et appuyez Ctrl+G. Entrez 0044510C (l'adresse de signfinder ci-dessus + ImageBase de Link.exe qui est 400000). Alors le point culminant l'instruction s'ajoutant comme indiqué, clic droit avec la souris et sélectionnez binary>fill with NOPs:

Cela devrait ressembler à l'image ci-dessous:

Finalement clic droit de nouveau et sélectionnez Copy to exécutable>All modifications. Et le clic "copy all et le clic droit dans la nouvelle fenêtre qui s'ouvre et sélectionnez save file. Les autres versions de link.exe ont le même ordre de code à des emplacements différents qui sont patchés de la même manière.

Si nous utilisons le linker patché pour recompiler le même exemple de programme nous voyons que les octets supplémentaires ont disparus :

Les seules autres différences entre les 2 fichiers sont bien sûr e_lfanew (la compensation de l'en-tête PE), TimeDateStamp et SizeOfHeaders (qui est efficacement l' offset de la première section). Regardez mon PE tutorial pour plus de renseignements.

Assemblage Avec une extension shell

Ce trick (tour) vous permettra d'assembler des fichiers de .asm du menu en cliquant-droit dans l'explorateur qui peut sauver le temps de petits emplois rapides;-) < / la fonte > < p> < la taille de fonte = "3" > D'abord copiez et collez ce script dans votre texteditor, sauvez-le sous le nom de assemble.bat à l'emplacement C:\masm32\bin\ :

-----------copié&collé----------------

@if not exist %1.rc goto NoResource

rc /foRes.res %1.rc
cvtres /machine:ix86 /out:Res.obj Res.res
del Res.res
@if errorlevel 1 pause

ml /c /coff /Fo%1.obj %1
@if errorlevel 1 pause
link /SUBSYSTEM:WINDOWS %1.obj res.obj
@if errorlevel 1 pause
del %1.obj
del res.obj

@goto Exit

:NoResource
ml /c /coff /Fo%1.obj %1
@if errorlevel 1 pause
link /SUBSYSTEM:WINDOWS %1.obj
@if errorlevel 1 pause
del %1.obj

:Exit

-----------copié&collé----------------

Maintenant placez le patch variable dans le rep MASM. Pour WinXP, clic-droit sur Mon Ordinateur et selectionnez propriétés. Cliquez sur le label Advanced tab et ensuite le bouton Environnement Variables. CHEMIN de point culminant dans le carré inférieur , cliquez edit. Et ajoutez "C:\masm32\bin;" à la fin du patch de la variable. On devrait lire quelque chose comme:

%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;C:\masm32\bin;

Dans l'explorateur de Windows, allez dans tools>Folder Options>File. Cliquez "New" et "enter" à ".asm" dans la boîte d'Extension de Fichier. Maintenant cliquez "Advanced" et le tapez "Assembly Module" dans la première editbox. Cliquez "Newl" et tapez "Assemble" dans la boîte d'Action. Et pour finir cliquez "Browse" et naviguez jusqu'à assemble.bat dans l'application..

Et finalement dans l'explorateur de Windows, allez dans Tools>Folder Options>File Maintenant si vous cliquez droit sur un fichier .asm dans l'explorateur de Windows, vous devriez avoir l'option pour compiler. Le fichier batch compilera aussi et se liera dans un script de ressource.


ARTeam Tutorial

FAQ de Syntaxes MASM

Sommaire:

1. ADDR et OFFSET
2. Les crochets
3. L'opérateur PTR
4. Structures
5. Les constructions à un haut niveau dans MASM
6. Les opérateurs relationnels
7. Préservation des Registres

ADDR et OFFSET

L'OFFSET renvoie à l'adresse d'une variable. Il est utilisé pour spécifier la localisation plutôt que le contenu de la variable:

.data
MyVar db 77h ; Variable taille d'octet appelée MyVar initialisée à 77h

.code
mov eax, MyVar ; copie 77h en eax
mov ebx, offset MyVar ; copie l'adresse mémoire où 77h est stocké dans ebx

L'Offset peut aussi passer l'adresse d'une variable à une fonction en invoquant une instruction. Cependant, ça fonctionnera seulement pour des variables globales déclarées dans la section des .data ou .data? . Cela échouera avec des variables locales que vous déclarez sur l'entrée de votre proc d'utilisation de la déclaration LOCALE. Ceux-ci n'ont aucune compensation comme ils sont créés sur la pile au moment de l'exécution. .

L'opérateur ADDR résout ce problème. Il est utilisé exclusivement avec invoke pour passer l'adresse d'une variable à une fonction. Pour des variables globales il résume à une simple instruction PUSH ,pareil que si l'OFFSET avait été utilisée :

push GlobalVar

Cependant pour des variables locales ADDR traduit :

lea eax, LocalVar ; Il charge l'adresse effective de LocalVar dans eax
push eax

(Il est important de se rappeler qu'en utilisant addr avec des variables locales, le registre eax est utilisé plutôt que le laisser libre pour d'autre utilisation dans la procédure.)

Lea eax, LocalVar équivaux généralement à mov eax,offset LocalVar mais c' est 1 cpu cycle plus lent donc mov ... l'offset est préférée dans certains cas alors que d'autres des variables locales.

Les crochets

Les crochets indiquent généralement la valeur (le contenu) d'une variable par opposition à son adresse. Cependant, la syntaxe MASM diffère légèrement à celle d'autres assembleurs à cet égard. Dans MASM tout ceux-ci produisent la même instruction:

mov eax,1
mov eax,[1]
mov eax, DWORD PTR 1
mov eax, DWORD PTR [1]

MyVariable & [MyVariable]Les deux signifient la valeur de MyVariable

Beaucoup de programmeurs utilisent par habitude des crochets avec des variables pour dénoter le contenu ainsi cela rend le code source un peu plus lisible et il le rend plus facile au code d'être supporté par d'autres assembleurs.

Comme il a été dit ci-dessus, l'offset MyVariable & addr MyVariable les deux signifient l'adresse de MyVariable.

Quand c'est utilisé avec les registres, cependant, les crochets font vraiment la différence et ont tendance à pointer l'adresse mémoire :

mov ebx,eax ; Copie la valeur de eax dans ebx.

mov ebx,[eax] ; Copie la valeur à l'adresse mémoire de eax dans ebx.

mov [ebx],eax ; Copie la valeur de eax en mémoire à l'adresse dans ebx.

L'opérateur PTR

L'opérateur PTR dit à l'assembleur la taille des données dans des situations où la taille ne peut pas être tirée automatiquement. Par exemple :

MOV [eax], 0

Ce n'est pas une instruction valable parce que l'opérande de mémoire [eax] n'indique pas de taille. Si vous voulez copier le zéro de valeur dans un OCTET de la mémoire à [eax], vous utilisez l'OCTET la notation PTR donc l'assembleur sait combien utilise la mémoire. Pour vous aider à éclaircir cette confusion voici quelques exemples de base :

mov bl, [eax]

Ici, le compilateur sait que 'bl' fait 8 (bits) et vous chargez le processeur de remplir 'bl' de la valeur ([...]) trouvé à l'adresse d'eax. Puisque le compilateur sait que la limite est de 8 (bits), impliqués par le registre 'bl' dans la destination, il n'y a aucun problème.

mov bl, byte ptr [eax]

C'EST EXACTEMENT LA MÊME CHOSE . C'est inutile, mais peut-être plus lisible. Ainsi pourquoi voudriez-vous utiliser l'octet ptr ou le mot ptr précédant un "at-the-address-of" ([...]) la déclaration ? On voit la réponse ici :

mov [ebx], 3

Voici un dilemme pour le compilateur. Il dit "le mouvement la valeur 3 à l'emplacement de mémoire à l'adresse d'ebx". Le compilateur aime penser dans des (bits) et doit savoir si 3 est un OCTET, UN WORD ou un DWORD etc. Il ne sait pas combien de(bits) de mémoire pour utiliser pour stocker la valeur donnée!! Pour le clarifier, BYTE PTR est utilisé pour stocker 3 à un octet de mémoire à l'adresse indiquée. De même le WORD PTR utiliserait 2 octets de mémoire pour stocker 3 en mémoire :
mov byte ptr [ebx], 3

Si vous pensez que vous devez utiliser l'opérateur PTR, vous demander si le CPU peut calculer combien de (bits) pour se déplacer, par une certaine indication DIRECTE (comme dans l'exemple 'bl' ci-dessus).

Structures

Déclarer un type de structure :

Exemple STRUCT
Champ1
Champ2
Champ3
Exemple ENDS

Définir un cas de structure de variable non initialisée :

.data?
MyStruct1 Exemple <>

Ou un cas initialisé de la variable :

.data
MyStruct2 Exemple <5,67,89>

Avoir accès aux champs directement :

mov eax,Mystruct2.field2 ; copie 67 dans eax

Avoir accès aux champs indirectement :

mov ebx,offset MyStruct2 ;Ebx contient le pointeur sur la structure de MyStruct2

Syntaxe1:

mov eax,[ebx].Exemple.Champ2

Syntaxe2:

mov eax,[ebx.Exemple.Champ2]

Syntaxe3:

mov eax,[Exemple ptr eax].Champ2

Syntaxe4 - Utilisation de la directive ASSUME :

assume eax:ptr Exemple
mov [eax].Champ2
assume eax:nothing ; Rappelez-vous toujours un-assume quand c'est fini .

Voir le chapitre sur Structures de Données pour avoir un exemple d'accès aux champs dans des structures empilées.

Les constructions à un haut niveau dans MASM

MASM tient compte de plusieurs types de C de haut niveau et construit pour rendre le code source plus lisible. L'assembleur traduit ceux-ci en asm des instructions avant l'assemblage dans le code d'objet. Le .if - .elseif - .else - .endif ont été utilisés largement dans nos Procédures de Dialogue jusqu'ici :

Opérateurs Relationnels

Vous pouvez exprimer les conditions du .IF.REPEAT et des directives .WHILE utilisant des opérateurs relationnels le même comme dans C. MASM traduit ceux-ci pour comparer, évaluer et des instructions de saut conditionnelles. Ils incluent :

Une condition sans opérateurs évalue pour le nonzéro comme il le fait en C :
.WHILE (x) Est identique à .WHILE (x != 0) - while x est nonzero
.WHILE (!x) Est identique à .WHILE (x == 0) - while x est zero

Vous pouvez aussi utiliser des noms de flag (le ZÉRO ?, CARRY ?, OVERFLOW ?, SIGN ? Et PARITY ?) comme les opérandes eg. in .WHILE (CARRY?), la valeur du flag CARRY détermine le résultat de la condition.

Préservation des Registres

Une fonction de Win32 que vous appelez préservera toujours les registres de segment
et d'EBX, EDI, ESI et des registres d'EBP. Au contraire, ECX et EDX considèrent
Les registres scratch sont toujours non définis et reviennent d'une fonction de Win32.
EAX n'est jamais préservé non plus et comme nous avons vu ce que l'on dit, cela la plupart du temps
Contient une valeur de retour. Autrement, c'est vide. Donc si vous avez des valeurs importantes dans EAX, ECX ou EDX, vous devriez les préserver (pour les sauver sur la pile) avant l'appel d'une fonction d'API.

De même si vous utilisez EBX, EDI, ESI ou EBP dans votre fonction de rappel de service, n'oubliez jamais de les rétablir avant le contrôle retour Windows. Cela ne signifie pas que vous ne pouvez pas utiliser ces quatre registres, vous le pouvez. Assurez-vous juste de les rétablir en arrière avant le contrôle passant en revenant à Windows.


ARTeam Tutorial

Capture d'écran d'un Patch

J'ai inclus ici une capture d'écran d'un patch Diablo2oo2 (Rechercher et Remplacer Patch Engine) chargé dans WinAsm seulement pour la référence rapide. Ce logiciel peut être téléchargé à partir de son site dans un fichier zip ici:

http://diablo2oo2.di.funpic.de/downloads/dup.search.and.replace.patchengine.sourcecode.rar


validate.asm

.386
.model	flat, stdcall
option	casemap :none

include		windows.inc
include		user32.inc
include		kernel32.inc
include		comdlg32.inc
includelib	user32.lib
includelib	kernel32.lib
includelib	comdlg32.lib

DlgProc		PROTO		:DWORD,:DWORD,:DWORD,:DWORD
Validate	PROTO		:DWORD

.data
strFilter	db		"Executable Files (*.exe, *.dll)",0,
                "*.exe;*.dll",0,"All Files",0,"*.*",0,0
OpenError	db		"Unable to open target file",0
MsgBoxCap	db		"Results",0
Invalid		db		"This is not a valid PE file!!",0
Valid		db		"This is a valid PE file!!",0

.data?
ofn		OPENFILENAME	<>
hInstance	HINSTANCE	?
hTarget		dd		?
hMapping	dd		?
pMapping	dd		?
TargetName	db		512 dup(?)

.const
IDD_MAIN	equ		1001
IDC_TARGET	equ		1003
IDC_OPEN	equ		1004
IDC_GO 		equ		1005
IDC_EXIT 	equ		1006
ARIcon		equ		2001

.code
start:
    invoke	GetModuleHandle, NULL
    mov	hInstance, eax
    invoke	DialogBoxParam,hInstance,IDD_MAIN,0,addr DlgProc,0
    invoke	ExitProcess, eax

DlgProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    mov	eax,uMsg
    .if	eax==WM_INITDIALOG
        invoke	LoadIcon,hInstance,2001
        invoke	SendMessage,hWin,WM_SETICON,1,eax
    .elseif eax==WM_COMMAND
        mov	eax,wParam
        .if	eax==IDC_OPEN
            mov ofn.lStructSize,SIZEOF ofn
            mov ofn.lpstrFilter,offset strFilter
            mov ofn.lpstrFile,offset TargetName
            mov ofn.nMaxFile,512
            mov ofn.Flags,OFN_FILEMUSTEXIST+OFN_PATHMUSTEXIST+\
                        OFN_LONGNAMES+OFN_EXPLORER+OFN_HIDEREADONLY
            invoke GetOpenFileName,addr ofn
            .if eax==TRUE
                invoke SetDlgItemText,hWin,IDC_TARGET,addr TargetName
                invoke RtlZeroMemory,addr TargetName,512
            .endif
        .elseif eax==IDC_GO
            invoke GetDlgItemText,hWin,IDC_TARGET,addr TargetName,512
            invoke lstrlen,addr TargetName
            .if eax!=0
                invoke Validate,addr TargetName
            .endif
        .elseif eax==IDC_EXIT
            invoke SendMessage,hWin,WM_CLOSE,0,0
        .endif
    .elseif	eax==WM_CLOSE
        invoke	EndDialog,hWin,0
    .endif
    xor	eax,eax
    ret
DlgProc endp

Validate proc FileName:DWORD
    invoke CreateFile,FileName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
    .if eax!=INVALID_HANDLE_VALUE
        mov hTarget,eax
        invoke CreateFileMapping,eax,0,PAGE_READ,0,0,0
        mov hMapping,eax
        invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0
        mov pMapping,eax
        .if [eax.IMAGE_DOS_HEADER.e_magic]==IMAGE_DOS_SIGNATURE
            add eax,[eax.IMAGE_DOS_HEADER.e_lfanew]
            .if [eax.IMAGE_NT_HEADERS.Signature]==IMAGE_NT_SIGNATURE
                invoke MessageBox,0,addr Valid,addr MsgBoxCap,MB_ICONASTERISK
            .endif
        .else
            invoke MessageBox,0,addr Invalid,addr MsgBoxCap,MB_ICONASTERISK
        .endif
        invoke UnmapViewOfFile,pMapping
        invoke CloseHandle,hMapping
        invoke CloseHandle,hTarget
    .else
        invoke MessageBox,0,addr OpenError,0,0
    .endif
    xor eax,eax
    Ret
Validate EndP

end start


validate.rc

#define IDD_DLG1001 1001
#define IDC_STATIC1002 1002
#define IDC_OPEN 1004
#define ARIcon 2001
#define IDC_TARGET 1003
#define IDC_EXIT 1006
#define IDC_GO 1005

ARIcon ICON DISCARDABLE "C:/Code Library/Projects/asm tut/PEValidate/favicon.ico"
1 24 DISCARDABLE "C:/Code Library/Projects/asm tut/PEValidate/XPVisualStyle.xml"

IDD_DLG1001 DIALOGEX 0,0,217,71
CAPTION "PE Validator"
FONT 8,"MS Sans Serif"
STYLE 0x10cc0800
EXSTYLE 0x00000000
BEGIN
    CONTROL "Target:",IDC_STATIC1002,"Static",0x50000201,10,15,24,15,0x00000000
    CONTROL "",IDC_TARGET,"Edit",0x50010080,37,15,144,16,0x00000200
    CONTROL ". . .",IDC_OPEN,"Button",0x50010000,187,15,17,16,0x00000000
    CONTROL "GO!",IDC_GO,"Button",0x50010000,37,43,51,16,0x00000000
    CONTROL "EXIT",IDC_EXIT,"Button",0x50010000,130,43,51,16,0x00000000
END


tELock.def

LIBRARY tELock
EXPORTS Trace


tELock.asm

; #########################################################################
; tELock.asm : Plugin for ImpREC to find tELock0.92x real API in its wrapped code
;
; Very quick and simple example to get all values in "push [API]; ... garbage ... ;ret"
; Note that this example is not a tracer but just an opcode checker.
; #########################################################################

.386
.model flat, stdcall
option casemap :none

include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib

.code

LibMain proc hInstDLL:DWORD, reason:DWORD, unused:DWORD
    ret
LibMain Endp

; ##########################################################################
; Parameters for Exported function:
;

; <hFileMap>    : HANDLE of the mapped file
; <dwSizeMap>   : Size of that mapped file
; <dwTimeOut>   : TimeOut of ImpREC in Options
; <dwToTrace>   : Pointer to trace (in VA)
; <dwExactCall> : EIP of the exact call (in VA)
;
; Returned value (in eax):
;

; Use a value greater or equal to 200. It will be shown by ImpREC if no output were created
; ##########################################################################

Trace proc hFileMap:DWORD, dwSizeMap:DWORD, dwTimeOut:DWORD, dwToTrace:DWORD, dwExactCall:DWORD

LOCAL dwPtrOutput : DWORD
LOCAL dwErrorCode : DWORD

    push ebx					; save ebx before we start
    invoke MapViewOfFile,hFileMap,FILE_MAP_READ + FILE_MAP_WRITE,0,0,0
    test eax,eax				; if API returns zero...
    jnz map_ok
    mov eax,201					; ...can't map view so put error code 201 in eax...
    pop ebx						; ...restore ebx...
    ret							; and return

map_ok:
    mov dwPtrOutput,eax			; if successful get pointer to file view in dwPtrOutput
    cmp dwSizeMap,4				; ensure size of view >=4
    jae map_ok2
    mov dwErrorCode,203			; otherwise Invalid map size so put error code 203 in eax...
    jmp end2					; ...and jump to close handles and return

map_ok2:
    mov ebx,dwToTrace			; put Pointer to trace in ebx
    invoke IsBadReadPtr,ebx,4	; check if the pointer is a valid address
    test eax,eax				; test if result is zero (valid) or non-zero (invalid)
    jz ptr_ok1
    mov dwErrorCode,205			; if Invalid pointer put error code 205 in eax
    jmp end2					; and jump to close handles and return

ptr_ok1:
    cmp byte ptr[ebx],0FFh		; if pointer OK then check if bytes pointed at include 0FFh...
    jnz end_ok
    cmp byte ptr[ebx+1],035h	; ...and 035h (opcodes for push [XXXXXXXX])
    jnz end_ok
    mov ebx,[ebx+2]				; Get this [XXXXXXXX] API address in ebx
    invoke IsBadReadPtr,ebx,4	; Check if address is valid
    test eax,eax
    jz ptr_ok2
    mov dwErrorCode,205			; If not, put Invalid pointer error code in eax...
    jmp end2					; ...and jmp to close handles and return

ptr_ok2:						; If API address valid then write the found pointer into the mapped file
    mov ebx,[ebx]				; swap the memory address in ebx for the value at that address
    mov eax,dwPtrOutput			; put pointer to file view in eax
    mov [eax],ebx				; write bytes in ebx into file view (pointed at by eax)

end_ok:
    mov dwErrorCode, 200		; If all is well then put success code in eax and proceed to close handles...

end2:
    invoke UnmapViewOfFile,dwPtrOutput ; Unmap the view
    invoke CloseHandle,hFileMap;       ; Close the handle of the mapped file
    mov eax,dwErrorCode                ; Set error code as returned value
    pop ebx
    ret

Trace endp

End LibMain


SnRpatcher.asm

.386
.model flat,stdcall
option casemap:none

include		windows.inc
include		user32.inc
include		kernel32.inc
includelib	user32.lib
includelib	kernel32.lib

DlgProc		proto		:DWORD,:DWORD,:DWORD,:DWORD
List		proto		:DWORD,:DWORD
Patch		proto		:DWORD
Scan		proto

.data
TargetName	db		"SuperCleaner.exe",0
BackupName	db		"SuperCleaner.exe.bak",0
Sequence	db		75h,41h,0E8h,7Ah
SeqLen		db		4
WBuffer		db		0EBh,41h,0E8h,7Ah
PatchOffset	dd		2347Fh
StartNfo	db		"Place in same folder as target and click APPLY",0
Backup		db		"Backup made",0
Success		db		"Target patched successfully",0
Already		db		"Target already patched!",0
OpenError	db		"Unable to open target file",0
ReadError	db		"Error reading from target file",0
WriteError	db		"Error writing to target file",0
Version		db		"Incorrect target file version...",0
Searching	db		"Searching for byte sequence to patch...",0
NotFound	db		"Correct byte sequence not found",0
AboutTxt	db		"SuperCleaner 2.84 Patch",10,13
        db		"~Coded by Goppit~",10,13
        db		"Release Date 15/12/05",0
AboutCap	db		"NFO",0

.data?
hInstance	HINSTANCE	?
hTarget		HINSTANCE	?
hTargetMap	HINSTANCE	?
RBuffer		dd		?
BytesRead	db		?
BytesWritten	db		?
pMapView	dd		?
FileSize	dd		?
SearchOffset	dd		?

.const
IDD_PATCH	equ		1001
IDC_LISTBOX	equ		1002
IDC_APPLY	equ		1003
IDC_ABOUT	equ		1004
IDC_EXIT	equ		1005
IDC_CHECKBOX	equ		1006
ARIcon		equ		2002

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke DialogBoxParam, hInstance, IDD_PATCH, NULL, addr DlgProc, NULL
    invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .if uMsg==WM_INITDIALOG
        invoke LoadIcon,hInstance,ARIcon
        invoke SendMessage,hWnd,WM_SETICON,1,eax
        invoke List,hWnd,addr StartNfo
    .elseif uMsg==WM_COMMAND
        mov	eax,wParam
        .if eax==IDC_APPLY
            invoke Patch,hWnd
        .elseif eax==IDC_ABOUT
            invoke MessageBox,hWnd,addr AboutTxt,addr AboutCap,MB_ICONINFORMATION
        .elseif eax==IDC_EXIT
            invoke SendMessage,hWnd,WM_CLOSE,0,0
        .endif
    .elseif	uMsg==WM_CLOSE
        invoke	EndDialog,hWnd,0
    .endif
    xor	eax,eax
    ret
DlgProc endp

List proc hWnd:HWND, pMsg:DWORD
    invoke SendDlgItemMessage,hWnd,IDC_LISTBOX,LB_ADDSTRING,0,pMsg
    invoke SendDlgItemMessage,hWnd,IDC_LISTBOX,WM_VSCROLL,SB_BOTTOM,0
    Ret
List EndP

Patch proc hWnd:HWND
    invoke GetFileAttributes,addr TargetName
    .if eax!=FILE_ATTRIBUTE_NORMAL
        invoke SetFileAttributes,addr TargetName,FILE_ATTRIBUTE_NORMAL
    .endif
    invoke CreateFile,addr TargetName,\
                    GENERIC_READ+GENERIC_WRITE,\
                    FILE_SHARE_READ+FILE_SHARE_WRITE,\
                    NULL,\
                    OPEN_EXISTING,\
                    FILE_ATTRIBUTE_NORMAL,\
                    NULL
    .if eax!=INVALID_HANDLE_VALUE
        mov hTarget,eax
        invoke SendDlgItemMessage, hWnd, IDC_CHECKBOX, BM_GETCHECK, 0, 0
        .if eax==BST_CHECKED
            invoke CopyFile, addr TargetName, addr BackupName, TRUE
            invoke List,hWnd,addr Backup
        .endif
        invoke SetFilePointer,hTarget,PatchOffset,NULL,FILE_BEGIN
        invoke ReadFile,hTarget,addr RBuffer,4,addr BytesRead,NULL
        .if BytesRead==4
            mov eax,dword ptr [RBuffer]
            .if eax==dword ptr [Sequence]
                invoke SetFilePointer,hTarget,PatchOffset,NULL,FILE_BEGIN
                jmp @WRITE
            .elseif eax==dword ptr [WBuffer]
                invoke List,hWnd,addr Already
            .else
                invoke List,hWnd,addr Version
                invoke List,hWnd,addr Searching
                invoke Scan
                .if eax==0
                    invoke List,hWnd,addr NotFound
                .else
                    invoke SetFilePointer,hTarget,eax,NULL,FILE_BEGIN
                    jmp @WRITE
                .endif
            .endif
        .else
            invoke List,hWnd,addr ReadError
        .endif
    .else
        invoke List,hWnd,addr OpenError
    .endif
    invoke CloseHandle,hTarget
    Ret

    @WRITE:
        invoke WriteFile,hTarget,addr WBuffer,1,addr BytesWritten,NULL
        .if BytesWritten==1
            invoke List,hWnd,addr Success
        .else
            invoke List,hWnd,addr WriteError
        .endif
        Ret
Patch EndP

Scan proc	;returns offset of byte sequence in eax
    LOCAL ReturnValue	:DWORD
    mov ReturnValue,0
    invoke GetFileSize,hTarget,NULL
    mov FileSize,eax
    invoke CreateFileMapping,hTarget,NULL,PAGE_READWRITE,0,0,NULL
    mov hTargetMap,eax
    invoke MapViewOfFile,eax,FILE_MAP_WRITE,0,0,0
    mov pMapView,eax
    mov edi,pMapView
    mov ecx,FileSize
    mov esi,offset Sequence
    xor eax,eax
    mov al,byte ptr [esi]

    @@:	repnz scasb
        cmp ecx,0
        jz @NOTFOUND
        push ecx
        push esi
        push edi
        dec edi
        mov ecx,4
        repz cmpsb
        cmp ecx,0
        jz @FOUND
        pop edi
        pop esi
        pop ecx
        jmp @b

    @FOUND:
        pop edi
        pop esi
        pop ecx
        inc ecx
        mov eax,FileSize
        sub eax,ecx
        mov ReturnValue,eax
        jmp @RETURN

    @NOTFOUND:
        mov ReturnValue,0
        jmp @RETURN

    @RETURN:
        invoke UnmapViewOfFile,pMapView
        invoke CloseHandle,hTargetMap
        mov eax,ReturnValue
        Ret
Scan EndP

end start


skeleton.asm

.386
.model flat, stdcall
option casemap:none

include kernel32.inc
includelib kernel32.lib

.data
.data?
.const

.code
start:
invoke ExitProcess,0
end start


simplekeygen.rc

#define IDC_STATIC1007 1007
#define IDC_STATIC1008 1008
#define IDC_NAME 1002
#define IDC_SERIAL 1003
#define IDC_GENERATE 1004
#define IDC_COPY 1005
#define IDC_EXIT 1006
#define IDD_KEYGEN 1001
#define ARIcon 2001

ARIcon ICON DISCARDABLE "favicon.ico"

IDD_KEYGEN DIALOGEX 0,0,221,110
CAPTION "ARTeam Keygen"
FONT 8,"MS Sans Serif"
STYLE 0x10cc0800
EXSTYLE 0x00000000
BEGIN
    CONTROL "",IDC_NAME,"Edit",0x50010080,44,15,163,13,0x00000200
    CONTROL "",IDC_SERIAL,"Edit",0x50010880,44,43,163,13,0x00000200
    CONTROL "Generate",IDC_GENERATE,"Button",0x50010000,10,71,61,22,0x00000000
    CONTROL "Copy",IDC_COPY,"Button",0x50010000,80,71,61,22,0x00000000
    CONTROL "Exit",IDC_EXIT,"Button",0x50010000,150,71,61,22,0x00000000
    CONTROL "Name:",IDC_STATIC1007,"Static",0x50000200,13,15,21,13,0x00000000
    CONTROL "Serial:",IDC_STATIC1008,"Static",0x50000200,13,43,21,13,0x00000000
END


simplekeygen.asm

.386
.model flat,stdcall
option casemap:none

include		windows.inc
include		kernel32.inc
include		user32.inc
includelib	kernel32.lib
includelib	user32.lib

DlgProc		proto		:DWORD,:DWORD,:DWORD,:DWORD

.data?
hInstance	HINSTANCE	?
NameBuffer	db		32 dup(?)
SerialBuffer	db		32 dup(?)

.const
IDD_KEYGEN	equ		1001
IDC_NAME	equ		1002
IDC_SERIAL	equ		1003
IDC_GENERATE	equ		1004
IDC_COPY	equ		1005
IDC_EXIT	equ		1006
ARIcon		equ		2001

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke DialogBoxParam, hInstance, IDD_KEYGEN, NULL, addr DlgProc, NULL
    invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg == WM_INITDIALOG
    invoke	LoadIcon,hInstance,ARIcon
    invoke	SendMessage,hWnd,WM_SETICON,1,eax
    invoke GetDlgItem,hWnd,IDC_NAME
    invoke SetFocus,eax
.elseif uMsg == WM_COMMAND
    mov	eax,wParam
    .if eax==IDC_GENERATE
        invoke GetDlgItemText,hWnd,IDC_NAME,addr NameBuffer,32
        call Generate
        invoke SetDlgItemText,hWnd,IDC_SERIAL,addr SerialBuffer
    .elseif eax==IDC_COPY
        invoke SendDlgItemMessage,hWnd,IDC_SERIAL,EM_SETSEL,0,-1
        invoke SendDlgItemMessage,hWnd,IDC_SERIAL,WM_COPY,0,0
    .elseif eax==IDC_EXIT
        invoke	SendMessage,hWnd,WM_CLOSE,0,0
    .endif
.elseif	uMsg == WM_CLOSE
    invoke	EndDialog,hWnd,0
.endif
    xor	eax,eax
    ret
DlgProc endp

Generate proc
invoke lstrlen, addr NameBuffer
test eax, eax
jle NOINPUT
mov ecx, eax
mov esi, offset NameBuffer
mov edi, offset SerialBuffer
@@:
    dec ecx
    mov dl, BYTE ptr [esi+ecx]
    mov BYTE ptr[edi], dl
    inc edi
    or ecx, ecx
    ja @b
NOINPUT:
    ret
Generate endp

end start


region.rc

#define IDD_DLG1001 1001
#define IDC_BUTTON1002 1002

2001 BITMAP DISCARDABLE "tweety78.bmp"
2002 RCDATA DISCARDABLE "region.rgn"

IDD_DLG1001 DIALOGEX 0,0,200,186
FONT 8,"MS Sans Serif"
STYLE 0x90000800
EXSTYLE 0x00000000
BEGIN
    CONTROL "?",IDC_BUTTON1002,"Button",0x50010000,163,132,10,10,0x00000000
END


region.asm

.386
.model flat,stdcall
option casemap:none

include		windows.inc
include		user32.inc
include		kernel32.inc
include		gdi32.inc
includelib	user32.lib
includelib	kernel32.lib
includelib	gdi32.lib

DlgProc		proto		:DWORD,:DWORD,:DWORD,:DWORD

.data
caption		db		"About",0
message		db		"How to apply a complex",13,10,"region to a simple",13,10,"dialog box.",0

.data?
hInstance	dd		?
hResInfo	dd		?
hResData	dd		?
pRsrc		dd		?
sizeRsrc	dd		?
brush		dd		?

.code
start:
    invoke GetModuleHandle, NULL
    mov hInstance,eax
    invoke DialogBoxParam, hInstance, 1001, NULL, addr DlgProc, NULL
    invoke ExitProcess,0

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .if uMsg == WM_INITDIALOG
        invoke SetWindowPos,hWnd, HWND_TOPMOST,0,0,0,0,SWP_NOACTIVATE + SWP_NOMOVE + SWP_NOSIZE
        invoke LoadBitmap,hInstance,2001
        invoke CreatePatternBrush,eax
        mov brush, eax
        invoke FindResource,hInstance,2002,RT_RCDATA
        mov hResInfo,eax
        invoke LoadResource,hInstance,hResInfo
        mov hResData, eax
        invoke SizeofResource,hInstance,hResInfo
        mov	sizeRsrc,eax
        invoke LockResource,hResData
        mov pRsrc, eax
        invoke ExtCreateRegion,NULL,sizeRsrc,pRsrc
        invoke SetWindowRgn,hWnd,eax,TRUE
    .elseif uMsg==WM_CTLCOLORDLG
        mov eax, brush
        ret
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        .if eax==1002
            invoke MessageBox,hWnd,addr message,addr caption,MB_ICONASTERISK
        .endif
    .elseif uMsg==WM_LBUTTONDBLCLK
        invoke SendMessage,hWnd,WM_CLOSE,0,0
    .elseif uMsg==WM_LBUTTONDOWN
        invoke SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,lParam
    .elseif	uMsg == WM_CLOSE
        invoke DeleteObject,brush
        invoke EndDialog, hWnd, 0
    .endif
    xor	eax,eax
    ret
DlgProc endp

end start


patcher.rc

#define IDD_DLG1001 1001
#define IDC_LISTBOX1002 1002
#define IDC_CHECKBOX1006 1006
#define IDC_IMAGE1007 1007
#define LOGO 2001
#define IDC_STATIC1008 1008
#define IDC_APPLY 1003
#define IDC_ABOUT 1004
#define IDC_EXIT 1005

LOGO BITMAP DISCARDABLE "Patch_Logo_1.bmp"

IDD_DLG1001 DIALOGEX 0,0,241,126
CAPTION "ARTeam Patch"
FONT 8,"MS Sans Serif"
STYLE 0x10cc0000
EXSTYLE 0x00000000
BEGIN
    CONTROL "",IDC_LISTBOX1002,"ListBox",0x50010140,4,49,233,34,0x00000200
    CONTROL "APPLY",IDC_APPLY,"Button",0x50010000,10,86,57,19,0x00000000
    CONTROL "ABOUT",IDC_ABOUT,"Button",0x50010000,90,86,57,19,0x00000000
    CONTROL "EXIT",IDC_EXIT,"Button",0x50010000,173,86,57,19,0x00000000
    CONTROL "IDC_CHECKBOX1006",IDC_CHECKBOX1006,"Button",0x50010003,17,111,11,10,0x00000000
    CONTROL "#2001",IDC_IMAGE1007,"Static",0x5000020e,0,0,243,47,0x00000000
    CONTROL "Make Backup",IDC_STATIC1008,"Static",0x50000000,37,111,47,10,0x00000000
END


offsetpatcher.asm

.386
.model flat,stdcall
option casemap:none

include		windows.inc
include		user32.inc
include		kernel32.inc
includelib	user32.lib
includelib	kernel32.lib

DlgProc		proto		:DWORD,:DWORD,:DWORD,:DWORD
Patch		proto		:DWORD
List		proto		:DWORD,:DWORD

.data
TargetName	db		"SuperCleaner.exe",0
BackupName	db		"SuperCleaner.exe.bak",0
Sequence	db		75h,41h,0E8h,7Ah
WBuffer		db		0EBh,41h,0E8h,7Ah
PatchOffset	dd		2347Fh
StartNfo	db		"Place in same folder as target and click APPLY",0
Backup		db		"Backup made",0
Success		db		"Target patched successfully",0
Already		db		"Target already patched!",0
OpenError	db		"Unable to open target file",0
ReadError	db		"Error reading from target file",0
WriteError	db		"Error writing to target file",0
Version		db		"Incorrect target file version",0
AboutTxt	db		"SuperCleaner 2.84 Patch",10,13
        db		"~Coded by Goppit~",10,13
        db		"Release Date 15/12/05",0
AboutCap	db		"NFO",0

.data?
hInstance	HINSTANCE	?
hTarget		HINSTANCE	?
RBuffer		dd		?
BytesRead	db		?
BytesWritten	db		?

.const
IDD_PATCH	equ		1001
IDC_LISTBOX	equ		1002
IDC_APPLY	equ		1003
IDC_ABOUT	equ		1004
IDC_EXIT	equ		1005
IDC_CHECKBOX	equ		1006
ARIcon		equ		2002

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke DialogBoxParam, hInstance, IDD_PATCH, NULL, addr DlgProc, NULL
    invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .if uMsg==WM_INITDIALOG
        invoke LoadIcon,hInstance,ARIcon
        invoke SendMessage,hWnd,WM_SETICON,1,eax
        invoke List,hWnd,addr StartNfo
    .elseif uMsg==WM_COMMAND
        mov	eax,wParam
        .if eax==IDC_APPLY
            invoke Patch,hWnd
        .elseif eax==IDC_ABOUT
            invoke MessageBox,hWnd,addr AboutTxt,addr AboutCap,MB_ICONINFORMATION
        .elseif eax==IDC_EXIT
            invoke SendMessage,hWnd,WM_CLOSE,0,0
        .endif
    .elseif	uMsg==WM_CLOSE
        invoke	EndDialog,hWnd,0
    .endif
    xor	eax,eax
    ret
DlgProc endp

List proc hWnd:HWND, pMsg:DWORD
    invoke SendDlgItemMessage,hWnd,IDC_LISTBOX,LB_ADDSTRING,0,pMsg
    invoke SendDlgItemMessage,hWnd,IDC_LISTBOX,WM_VSCROLL,SB_BOTTOM,0
    Ret
List EndP

Patch proc hWnd:HWND
    invoke GetFileAttributes,addr TargetName
    .if eax!=FILE_ATTRIBUTE_NORMAL
        invoke SetFileAttributes,addr TargetName,FILE_ATTRIBUTE_NORMAL
    .endif
    invoke CreateFile,addr TargetName,\
                    GENERIC_READ+GENERIC_WRITE,\
                    FILE_SHARE_READ+FILE_SHARE_WRITE,\
                    NULL,\
                    OPEN_EXISTING,\
                    FILE_ATTRIBUTE_NORMAL,\
                    NULL
    .if eax!=INVALID_HANDLE_VALUE
        mov hTarget,eax
        invoke SendDlgItemMessage, hWnd, IDC_CHECKBOX, BM_GETCHECK, 0, 0
        .if eax==BST_CHECKED
            invoke CopyFile, addr TargetName, addr BackupName, TRUE
            invoke List,hWnd,addr Backup
        .endif
        invoke SetFilePointer,hTarget,PatchOffset,NULL,FILE_BEGIN
        invoke ReadFile,hTarget,addr RBuffer,4,addr BytesRead,NULL
        .if BytesRead==4
            mov eax,dword ptr [RBuffer]
            .if eax==dword ptr [Sequence]
                invoke SetFilePointer,hTarget,PatchOffset,NULL,FILE_BEGIN
                invoke WriteFile,hTarget,addr WBuffer,1,addr BytesWritten,NULL
                .if BytesWritten==1
                    invoke List,hWnd,addr Success
                .else
                    invoke List,hWnd,addr WriteError
                .endif
            .elseif eax==dword ptr [WBuffer]
                invoke List,hWnd,addr Already
            .else
                invoke List,hWnd,addr Version
            .endif
        .else
            invoke List,hWnd,addr ReadError
        .endif
    .else
        invoke List,hWnd,addr OpenError
    .endif
    invoke CloseHandle,hTarget
    Ret
Patch EndP

end start


MyDLL.asm

.386
.model flat, stdcall
option casemap:none

include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib

.data
Caption db "Exported Function",0
Text1	db "You called function 1 from MyDLL",0
Text2	db "You called function 2 from MyDLL",0

.code

DLLEntry proc hInstDLL:DWORD, reason:DWORD, unused:DWORD
    mov eax,TRUE			; no conditions needed here
    Ret				; no need to save hInstDLL
DLLEntry Endp				; simply put TRUE in EAX to continue loading the DLL

_ExpFunc1 proc
    invoke MessageBox,0,addr Text1,addr Caption,MB_OK
    Ret
_ExpFunc1 EndP

_ExpFunc2 proc
    invoke MessageBox,0,addr Text2,addr Caption,MB_OK
    Ret
_ExpFunc2 EndP

end DLLEntry


helloworld

.386
.model flat, stdcall
option casemap:none

include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib

.data
MsgBoxCaption  db "Simple MessageBox Program",0
MsgBoxText     db "Hello World!",0

.code
start:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess,0
end start


loader1.asm

.386
.model flat,stdcall
option casemap:none

include			windows.inc
include			kernel32.inc
include			user32.inc
includelib		kernel32.lib
includelib		user32.lib

.data?
StartupInfo		STARTUPINFO		<>
ProcessInfo		PROCESS_INFORMATION 	<>
BytesWritten 		dd			?

.data
TargetName		db			"SuperCleaner.exe",0
ProcError		db			"Error starting target process.",0
WriteError		db			"Error writing to target process",0
PatchByte		db			0EBh
PatchVA			dd			0042374Fh

.code
start:
    invoke CreateProcess,addr TargetName,0,0,0,0,CREATE_SUSPENDED,0,0,addr StartupInfo,addr ProcessInfo
    .if eax==0
        invoke MessageBox,NULL,addr ProcError,NULL,MB_ICONERROR
    .else
        invoke WriteProcessMemory,ProcessInfo.hProcess,PatchVA,addr PatchByte,1,addr BytesWritten
        .if BytesWritten!=1
            invoke MessageBox,NULL,addr WriteError,NULL,MB_ICONERROR
            invoke TerminateProcess,ProcessInfo.hProcess,0
        .else
            invoke ResumeThread,ProcessInfo.hThread
        .endif
    .endif
    invoke ExitProcess,NULL
end start


loader2.asm

.386
.model flat,stdcall
option casemap:none

include			windows.inc
include			kernel32.inc
include			user32.inc
includelib		kernel32.lib
includelib		user32.lib

.data?
ThreadContext		CONTEXT			<>
StartupInfo		STARTUPINFO		<>
ProcessInfo		PROCESS_INFORMATION	<>
StopBuffer		dw			?
ReadBuffer		dd			?
Buffer			dd			?

.data
TargetName		db			"SuperCleaner.exe",0
ProcError		db			"Error starting target process. Is loader in same folder as target!",0
ReadError		db			"Error reading from target process",0
Version			db			"Incorrect target file version!",0
WriteError		db			"Error writing to target process",0
ContextError		db 			"GetThreadContext failed. Target process terminated?",0
Sequence		db			75h,41h,0E8h,0BAh
StopVA			dd			004863ECh
OEPBytes		db			0E9h,0E9h
StopCode		db			0EBh,0FEh
PatchVA			dd			0042374Fh
PatchByte		db			0EBh

.code
start:
    invoke CreateProcess,addr TargetName,0,0,0,0,CREATE_SUSPENDED,0,0,addr StartupInfo,addr ProcessInfo
    .if eax==0
        invoke MessageBox,0,addr ProcError,0,0
    .else
        invoke ReadProcessMemory,ProcessInfo.hProcess,StopVA,addr StopBuffer,2,addr Buffer
        mov ax,word ptr [StopBuffer]
        .if ax==word ptr [OEPBytes]
            invoke WriteProcessMemory,ProcessInfo.hProcess,StopVA,addr StopCode,2,addr Buffer
            invoke ResumeThread,ProcessInfo.hThread
            invoke Sleep,10
            mov ThreadContext.ContextFlags,CONTEXT_FULL
@@:
            invoke GetThreadContext,ProcessInfo.hThread,addr ThreadContext
            .if eax==0
                invoke MessageBox,0,addr ContextError,0,0
                jmp @Error
            .else
                mov eax,StopVA
                .if eax==ThreadContext.regEip
                    invoke SuspendThread,ProcessInfo.hThread
                    invoke ReadProcessMemory,ProcessInfo.hProcess,PatchVA,addr ReadBuffer,4,addr Buffer
                    mov eax,dword ptr [ReadBuffer]
                    .if eax==dword ptr [Sequence]
                        invoke WriteProcessMemory,ProcessInfo.hProcess,StopVA,addr OEPBytes,2,addr Buffer
                        invoke WriteProcessMemory,ProcessInfo.hProcess,PatchVA,addr PatchByte,1,addr Buffer
                        invoke ResumeThread,ProcessInfo.hThread
                        jmp @Exit
                    .else
                        invoke MessageBox,0,addr Version,0,0
                        jmp @Error
                    .endif
                .else
                    invoke Sleep,10
                    jmp @b
                .endif
            .endif
        .else
            invoke MessageBox,0,addr Version,0,0
        .endif
@Error:
        invoke TerminateProcess,ProcessInfo.hProcess,0
    .endif
@Exit:
    invoke ExitProcess,NULL
end start


IsDebug.Def

LIBRARY
EXPORTS
_ODBG_Plugininit
_ODBG_Plugindata
_ODBG_Pluginmenu
_ODBG_Pluginaction
_ODBG_Plugindestroy
_ODBG_Pluginreset
_ODBG_Pluginclose


IsDebug.asm

.586p
.model flat, stdcall  ; 32 bit memory model
option scoped         ; local labels are enabled, global labels inside
                      ; PROC should be defined with double colons (LABEL::)
option casemap :none  ; case sensitive

DlgDumpProc		proto :DWORD,:DWORD,:DWORD,:DWORD
DlgOptionProc   proto :DWORD,:DWORD,:DWORD,:DWORD

include windows.inc
include kernel32.inc
include user32.inc
include ..\plugin.inc
include masm32.inc
include comdlg32.inc

includelib kernel32.lib
includelib user32.lib
includelib ..\ollydbg.lib
includelib masm32.lib
includelib comdlg32.lib

    ;

    ; literal string MACRO
    ;

      literal MACRO quoted_text:VARARG
        LOCAL local_text
        .data
          local_text db quoted_text,0
        .code
        EXITM <local_text>
      ENDM

      CTEXT MACRO quoted_text:VARARG
        EXITM <offset literal(quoted_text)>
      ENDM

      m2m MACRO M1, M2
        push M2
        pop  M1
      ENDM

      return MACRO arg
        mov eax, arg
        ret
      ENDM

MAXSIZE equ 260

;Dumper.dlg
IDD_DUMP								equ 1000
IDC_BTCANCEL							equ 1001
IDC_BTDUMP								equ 1002
IDC_EDTSIZE								equ 1003
IDC_STC1								equ 1004
IDC_EDTOFFSET							equ 1005
IDC_STC2								equ 1006

;Res\Params.dlg
IDD_OPTION								equ 1001
IDC_GRP1								equ 1003
IDC_CHK1								equ 1001
IDC_STC3								equ 1002
IDC_SLEEPTIME							equ 1004
IDC_STC4								equ 1005
IDC_BTN1								equ 1006
IDC_BTN2								equ 1007

;IsDebug.rc

.data
the_byte            db 1
null_byte           db 0
ofn                 OPENFILENAME <>
FilterString        db "Bin Files",0    ;dont insert between
bin_extend          db "*.bin",0,0      ;here
template            db "%d",0
str_idb_Autoload    db "idb_Autoload",0
str_idb_Sleeptime   db "idb_Sleeptime",0

.data?

hinst               HINSTANCE        ?  ; DLL instance
hwmain              HWND             ?  ; Handle of main OllyDbg window
textbuffer          db 512 dup(?)
byte_location       dd ?
SizeWritten         dd ?
hFileWrite          dd ?
hMemory_code        dd ?
pMemory_code        dd ?
SVWinClass          db 32 dup(?)
svthreadid          dd ?
sleep_time          dd ?
auto_load           dd ?
dw_buffer           dd ?

.code

; Entry point into a plugin DLL. Many system calls require DLL instance
; which is passed to DllEntryPoint() as one of parameters. Remember it.
; Preferrable way is to place initializations into ODBG_Plugininit() and
; cleanup in ODBG_Plugindestroy().

DllEntryPoint proc hi:HINSTANCE, reason:dword, res:dword
        .IF reason == DLL_PROCESS_ATTACH
            m2m     hinst, hi               ; Mark plugin instance
        .ENDIF
        return  1                           ; Report success
DllEntryPoint endp

; ODBG_Plugindata() is a "must" for valid OllyDbg plugin. It must fill in
; plugin name and return version of plugin interface. If function is absent,
; or version is not compatible, plugin will be not installed. Short name
; identifies it in the Plugins menu. This name is max. 31 alphanumerical

; characters or spaces + terminating '\0' long. To keep life easy for users,
; this name should be descriptive and correlate with the name of DLL.

ODBG_Plugindata proc C shortname:ptr byte
        invoke  lstrcpy, shortname, CTEXT("IsDebugPresent")      ; Name of plugin
        return  PLUGIN_VERSION;
ODBG_Plugindata endp

; OllyDbg calls this obligatory function once during startup. Place all
; one-time initializations here. If all resources are successfully allocated,

; function must return 0. On error, it must free partially allocated resources
; and return -1, in this case plugin will be removed. Parameter ollydbgversion
; is the version of OllyDbg, use it to assure that it is compatible with your
; plugin; hw is the handle of main OllyDbg window, keep it if necessary.
; Parameter features is reserved for future extentions, do not use it.
ODBG_Plugininit proc C ollydbgversion:dword, hw:HWND, features:ptr dword

        ; Check that version of OllyDbg is correct.
        .IF ollydbgversion < PLUGIN_VERSION
            jmp     @@bad_exit
        .ENDIF
        invoke  Addtolist, 0, 0, CTEXT("IsDebugPresent plugin v1.4 (SV 2oo3)")

        ; Keep handle of main OllyDbg window. This handle is necessary, for example,
        ; to display message box.
        m2m     hwmain, hw
        return  0

@@bad_exit:
        return  -1
ODBG_Plugininit endp

; OllyDbg calls this optional function once on exit. At this moment, all MDI
; windows created by plugin are already destroyed (and received WM_DESTROY
; messages). Function must free all internally allocated resources, like
; window classes, files, memory and so on.
ODBG_Plugindestroy proc C
        invoke Unregisterpluginclass,addr SVWinClass
        ret
ODBG_Plugindestroy endp

; Function is called when user opens new or restarts current application.
; Plugin should reset internal variables and data structures to initial state.
ODBG_Pluginreset proc C
       invoke Pluginreadintfromini,hinst,addr str_idb_Autoload,0    ;Auto ?
       .if (eax!=0)
           invoke Pluginreadintfromini,hinst,addr str_idb_Sleeptime,1000
           mov sleep_time,eax   ;save time value

           lea eax,svthread
           invoke CreateThread,NULL,NULL,eax,NULL,NULL,svthreadid
       .endif
       ret

svthread:
       invoke Sleep,sleep_time                 ;Wait a little ;)
       invoke Plugingetvalue,VAL_PROCESSID     ;is something loaded ??
       .if (eax!=0)
;           invoke SetWindowText,hwmain,CTEXT("0llyDbg -")

           call get_byte_location
           mov byte_location,eax
           test eax,eax
           jz svthread

           ;1==Debugger 0==Clean ;)
           invoke Writememory,addr null_byte,byte_location,1,MM_RESTORE
           .if (eax!=1)
              ;ooopps
              invoke Error,CTEXT("Error WriteMemory failed")
           .elseif
              invoke  Addtolist, 0, -1, CTEXT("  IsDebugPresent hidden")
           .endif
           invoke ExitThread,TRUE
        .endif

ODBG_Pluginreset endp

; OllyDbg calls this optional function when user wants to terminate OllyDbg.
; All MDI windows created by plugins still exist. Function must return 0 if
; it is safe to terminate. Any non-zero return will stop closing sequence. Do
; not misuse this possibility! Always inform user about the reasons why
; termination is not good and ask for his decision!
ODBG_Pluginclose proc C
        ; For automatical restoring of open windows, mark in .ini file whether
        ; Bookmarks window is still open.
        return  0
ODBG_Pluginclose endp

; Function adds items either to main OllyDbg menu (origin=PM_MAIN) or to popup
; menu in one of standard OllyDbg windows. When plugin wants to add own menu
; items, it gathers menu pattern in data and returns 1, otherwise it must
; return 0. Except for static main menu, plugin must not add inactive items.

; Item indices must range in 0..63. Duplicated indices are explicitly allowed.
ODBG_Pluginmenu proc C uses ebx origin:dword, data:ptr byte, item:dword

        mov     eax, origin
        ; Menu creation is very simple. You just fill in data with menu pattern.
        ; Some examples:
        ; 0 Aaa,2 Bbb|3 Ccc|,,  - linear menu with 3 items, relative IDs 0, 2 and
        ;                         3, separator between second and third item, last
        ;
        ;                         separator and commas are ignored;
        ; #A{0Aaa,B{1Bbb|2Ccc}} - unconditional separator, followed by popup menu
        ;                         A with two elements, second is popup with two
        ;                         elements and separator inbetween.
        .IF eax == PM_MAIN ; Plugin menu in main window
            invoke  lstrcpy, data, CTEXT("0 &Hide,1 &Restore,2 &Option|3 &About|4 &Dumper")
            ;invoke  lstrcpy, data, CTEXT("0 &Hide,1 &Restore|2 &About|3 &Dumper,4 &Initier")
            ; If your plugin is more than trivial, I also recommend to include Help.
            return  1

        .ENDIF
        return  0
ODBG_Pluginmenu endp

; This optional function receives commands from plugin menu in window of type
; origin. Argument action is menu identifier from ODBG_Pluginmenu(). If user

; activates automatically created entry in main menu, action is 0.
ODBG_Pluginaction proc C origin:dword, action:dword, item:dword
        mov     eax, origin
        .IF eax == PM_MAIN
            mov     eax, action
            .IF !eax
                call get_byte_location
                mov byte_location,eax
                .if (eax)
                    ;1==Debugger 0==Clean ;)
                    invoke Writememory,addr null_byte,byte_location,1,MM_RESTORE
                    .if (eax!=1)
                        ;ooopps
                        invoke Error,CTEXT("Error WriteMemory failed")
                    .elseif
                      invoke  Addtolist, 0, -1, CTEXT("  IsDebugPresent hidden")
                    .endif
                .endif
            .ELSEIF eax == 1
                call get_byte_location
                mov byte_location,eax
                .if (eax)
                    ;resore original code
                    invoke Writememory,addr the_byte,byte_location,1,MM_RESTORE
                    .if (eax!=1)
                        ;ooopps
                        invoke Error,CTEXT("Error WriteMemory failed")
                    .elseif
                      invoke  Addtolist, 0, -1, CTEXT("  IsDebugPresent restored")
                    .endif
                .endif
            .ELSEIF eax == 2
                invoke DialogBoxParam, hinst, IDD_OPTION, hwmain, addr DlgOptionProc, NULL
            .ELSEIF eax == 3
                ; Menu item "About", displays plugin info.
                invoke  MessageBox, hwmain, CTEXT("IsDebuggerPresent plugin v1.4",13,10,"(IsDebuggerPresent byte Patcher)",13,10,"Copyright (C) 2oo3 SV",13,10,"MASM32 version"),\
                                            CTEXT("IsDebuggerPresent plugin"),MB_OK or MB_ICONINFORMATION
            .ELSEIF eax == 4
                invoke DialogBoxParam, hinst, IDD_DUMP, hwmain, addr DlgDumpProc, NULL
            .ENDIF
        .ENDIF
        ret
ODBG_Pluginaction endp
get_byte_location proc
    push ebx
    invoke Getcputhreadid
    .if (eax)
        invoke Findthread,eax                  ;retreive thread info
        assume eax:ptr t_thread
        push [eax].reg.base[4*4]               ;base of FS
        pop ebx
        add ebx,30h
        invoke Readmemory,addr dw_buffer,ebx,4,MM_RESTORE
        mov eax,dw_buffer
        add eax,2h
    .endif
    pop ebx
    ret

get_byte_location endp
DlgDumpProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL dump_start:DWORD
LOCAL dump_size:DWORD

    .IF uMsg == WM_INITDIALOG
        mov ofn.lStructSize,SIZEOF ofn
        push hWnd
        pop  ofn.hWndOwner
        push hinst
        pop  ofn.hInstance
        mov  ofn.nMaxFile,MAXSIZE
    .ELSEIF uMsg == WM_CLOSE
        invoke SendMessage, hWnd, WM_COMMAND, IDC_BTCANCEL, 0
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        mov edx,wParam
        shr edx,16
        .IF dx==BN_CLICKED
            .IF ax==IDC_BTCANCEL
                invoke EndDialog, hWnd, NULL
            .ELSEIF ax==IDC_BTDUMP
                invoke Plugingetvalue,VAL_PROCESSID     ;is something loaded ??
                .if (eax!=0)
                    pushad
                    invoke GetDlgItemText,hWnd,IDC_EDTOFFSET,addr textbuffer,10
                    invoke htodw,addr textbuffer
                    mov dump_start,eax
                    invoke GetDlgItemText,hWnd,IDC_EDTSIZE,addr textbuffer,10
                    invoke htodw,addr textbuffer
                    mov dump_size,eax

                    .if dump_start!=0 && dump_size!=0
                        ;Alloc Mem
                        invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,dump_size
                        mov  hMemory_code,eax
                        invoke GlobalLock,hMemory_code
                        mov  pMemory_code,eax
                        ;Read in Mem
                        invoke Readmemory,pMemory_code,dump_start,dump_size,MM_RESTORE
                        .if (eax==dump_size)
                            push hWnd
                            pop  ofn.hWndOwner
                            mov ofn.Flags,OFN_OVERWRITEPROMPT
                            mov  ofn.lpstrFilter, OFFSET FilterString
                            mov ofn.lpstrDefExt, OFFSET bin_extend
                            mov  ofn.lpstrFile, OFFSET textbuffer
                            mov  ofn.nMaxFile,MAXSIZE
                            mov [textbuffer],0  ;filename buffer a vide
                            invoke GetSaveFileName, ADDR ofn
                            .if eax==TRUE
                                invoke CreateFile,ADDR textbuffer,\
                                       GENERIC_READ or GENERIC_WRITE ,\
                                       FILE_SHARE_READ or FILE_SHARE_WRITE,\
                                       NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,\
                                       NULL
                                mov hFileWrite,eax
                                ;Save mem in file
                                invoke WriteFile,hFileWrite,pMemory_code,dump_size,ADDR SizeWritten,NULL
                                invoke CloseHandle,hFileWrite
                                invoke Flash,CTEXT("File successfully writted !!")
                              .endif
                          .endif
                          ;Free Mem
                        invoke GlobalUnlock,pMemory_code
                        invoke GlobalFree,hMemory_code
                    .endif
                     popad
                .else
                    invoke Error,CTEXT("Error Nothing loaded")
                .endif
            .ENDIF
        .ENDIF
    .ELSE
        mov eax, FALSE
        ret
    .ENDIF
    mov eax, TRUE
    ret
DlgDumpProc endp

DlgOptionProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

    .IF uMsg == WM_INITDIALOG
        ;read params from ini & update windows
        invoke Pluginreadintfromini,hinst,addr str_idb_Autoload,0
        mov auto_load,eax
        invoke Pluginreadintfromini,hinst,addr str_idb_Sleeptime,1000
        mov sleep_time,eax
        invoke wsprintf,addr textbuffer,offset template,sleep_time
        invoke SetDlgItemText,hWnd,IDC_SLEEPTIME,addr textbuffer
        .if (auto_load!=0)
            invoke GetDlgItem,hWnd,IDC_CHK1
            invoke SendMessage,eax,BM_SETCHECK,BST_CHECKED,0
        .endif
    .ELSEIF uMsg == WM_CLOSE
        invoke SendMessage, hWnd, WM_COMMAND, IDC_BTCANCEL, 0
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        mov edx,wParam
        shr edx,16
        .IF dx==BN_CLICKED
            .IF ax==IDC_BTN2        ;CANCEL
                invoke EndDialog, hWnd, NULL
            .ELSEIF ax==IDC_BTN1    ;SAVE
                invoke GetDlgItemText,hWnd,IDC_SLEEPTIME,addr textbuffer,10
                invoke atodw,addr textbuffer
                mov sleep_time,eax
                ;save params to ini
                invoke Pluginwriteinttoini,hinst,addr str_idb_Sleeptime,sleep_time
                invoke GetDlgItem,hWnd,IDC_CHK1
                invoke SendMessage,eax,BM_GETSTATE,0,0
                .if eax==BST_CHECKED
                    invoke Pluginwriteinttoini,hinst,addr str_idb_Autoload,1
                .elseif
                    invoke Pluginwriteinttoini,hinst,addr str_idb_Autoload,0
                .endif
               invoke Flash,CTEXT("Option saved !!")
              .ENDIF
        .ENDIF
    .ELSE
        mov eax, FALSE
        ret
    .ENDIF
    mov eax, TRUE
    ret
DlgOptionProc endp

end DllEntryPoint


Implicit.asm

.386
.model flat, stdcall
option casemap:none

include windows.inc
include kernel32.inc
include MyDLL.inc
includelib kernel32.lib
includelib MyDLL.lib

.code
start:
call _ExpFunc1
call _ExpFunc2	; note both functions are accessible by name because we have lib and inc files
invoke ExitProcess,0
end start


Explicit.asm

.386
.model flat, stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
LibName		db	"MyDLL",0
ProcName	db	"_ExpFunc1",0

.data?
hInstLib	HINSTANCE	?

.code
start:
invoke LoadLibrary,addr LibName
mov hInstLib,eax
invoke GetProcAddress,eax,addr ProcName
call eax
invoke GetProcAddress,hInstLib,2
call eax
invoke FreeLibrary,hInstLib
invoke ExitProcess,0
end start


DLLskeleton.asm

.386
.model flat, stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data

.data?

.code

DLLEntry proc hInstDLL:DWORD, reason:DWORD, unused:DWORD

    .if reason == DLL_PROCESS_ATTACH	; initialisation code for when DLL is loaded
        mov eax,TRUE			; put TRUE in EAX to continue loading the DLL
    .elseif reason == DLL_PROCESS_DETACH
        ; exit code for when DLL unloaded
    .elseif reason == DLL_THREAD_ATTACH
        ; code for when a thread is created in the process
    .elseif reason == DLL_THREAD_DETACH
        ; code for when a thread is destroyed in the process
    .endif
    Ret

DLLEntry Endp

ExpFunc1 proc
    ; code for first exported function here
    Ret
ExpFunc1 EndP

end DLLEntry


dialogbox1.rc

#define IDD_DLG1001 1001
#define IDC_EDIT1002 1002
#define IDC_BUTTON1003 1003
#define IDC_BUTTON1004 1004

IDD_DLG1001 DIALOGEX 0,0,221,127
CAPTION "Simple Dialog Box Program"
FONT 8,"MS Sans Serif"
STYLE 0x10cc0000
EXSTYLE 0x00000000
BEGIN
    CONTROL "",IDC_EDIT1002,"Edit",0x50010080,29,21,158,20,0x00000200
    CONTROL "Say Hello",IDC_BUTTON1003,"Button",0x50010000,19,80,79,22,0x00000000
    CONTROL "Exit",IDC_BUTTON1004,"Button",0x50010000,117,80,83,22,0x00000000
END


dialogbox1.asm

.386
.model flat,stdcall
option casemap:none

include		windows.inc
include		user32.inc
include		kernel32.inc
includelib	user32.lib
includelib	kernel32.lib

DlgProc		proto		:DWORD,:DWORD,:DWORD,:DWORD

.data
Message		db		"Hello World!",0

.data?
hInstance	HINSTANCE	?

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke DialogBoxParam, hInstance, 1001, NULL, addr DlgProc, NULL
    invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .if uMsg == WM_COMMAND
        mov	eax,wParam
        .if eax==1003
            invoke SetDlgItemText,hWnd,1002,ADDR Message
        .elseif eax==1004
            invoke	SendMessage, hWnd, WM_CLOSE, 0, 0
        .endif
    .elseif	uMsg == WM_CLOSE
        invoke	EndDialog, hWnd, 0
    .endif

    xor	eax,eax
    ret
DlgProc endp

end start


dialog2.rc

#define IDD_DLGBOX	1001
#define IDC_EDITBOX	1002
#define IDC_MESSAGE	1003
#define IDC_EXIT	1004
#define IDM_GETTEXT	1005
#define IDM_CLEARTEXT	1006
#define IDM_EXIT	1007
#define MyMenu		1008
#define ARIcon		2000

ARIcon ICON DISCARDABLE "favicon.ico"

MyMenu MENUEX DISCARDABLE
BEGIN
    POPUP "Menu",1009
    BEGIN
        MENUITEM "Get Text",IDM_GETTEXT
        MENUITEM "Clear Text",IDM_CLEARTEXT
        MENUITEM "",1010,0x00000800
        MENUITEM "Exit",IDM_EXIT
    END
END

IDD_DLGBOX DIALOGEX 0,0,221,127
CAPTION "Simple Dialog Box Program"
FONT 8,"MS Sans Serif"
MENU MyMenu
STYLE 0x10cc0800
EXSTYLE 0x00000000
BEGIN
    CONTROL "",IDC_EDITBOX,"Edit",0x50010080,30,22,158,20,0x00000200
    CONTROL "Say Hello",IDC_MESSAGE,"Button",0x50010000,19,80,79,22,0x00000000
    CONTROL "Exit",IDC_EXIT,"Button",0x50010000,117,80,83,22,0x00000000
END


dialog2.asm

.386
.model flat,stdcall
option casemap:none

include		windows.inc
include		user32.inc
include		kernel32.inc
includelib	user32.lib
includelib	kernel32.lib

DlgProc		proto		:DWORD,:DWORD,:DWORD,:DWORD

.data
Message		db		"Hello World!",0
Caption		db		"This is the text from the editbox",0

.data?
hInstance	HINSTANCE	?
buffer		db		512 dup(?)

.const
IDD_DLGBOX	equ		1001
IDC_EDITBOX	equ		1002
IDC_MESSAGE	equ		1003
IDC_EXIT	equ		1004
IDM_GETTEXT	equ		1005
IDM_CLEARTEXT	equ		1006
IDM_EXIT	equ		1007
ARIcon		equ		2000

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke DialogBoxParam, hInstance, IDD_DLGBOX, NULL, addr DlgProc, NULL
    invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg == WM_INITDIALOG
    invoke	LoadIcon,hInstance,ARIcon
    invoke	SendMessage, hWnd, WM_SETICON, 1, eax
    invoke GetDlgItem, hWnd,IDC_EDITBOX
    invoke SetFocus,eax
.elseif uMsg == WM_COMMAND
    mov	eax,wParam
    .if eax==IDC_MESSAGE
        invoke SetDlgItemText,hWnd,IDC_EDITBOX,ADDR Message
    .elseif eax==IDC_EXIT
        invoke	SendMessage, hWnd, WM_CLOSE, 0, 0
    .elseif eax==IDM_GETTEXT
        invoke GetDlgItemText,hWnd,IDC_EDITBOX,ADDR buffer,512
        invoke MessageBox,NULL,ADDR buffer,ADDR Caption,MB_OK
    .elseif eax==IDM_CLEARTEXT
        invoke SetDlgItemText,hWnd,IDC_EDITBOX,NULL
    .elseif eax==IDM_EXIT
        invoke	SendMessage, hWnd, WM_CLOSE, 0, 0
    .endif
.elseif	uMsg == WM_CLOSE
    invoke	EndDialog, hWnd, 0
.endif
    xor	eax,eax
    ret
DlgProc endp

end start


customkeygen.rc

#define IDC_COPY 1003
#define IDC_EXIT 1002
#define IDD_KEYGEN 1001
#define IDC_NAME 1005
#define IDC_SERIAL 1006
#define LOGO 2002
#define FAVICON 2001
#define IDC_IMAGE 1010
#define IDC_ABOUT 1004
#define IDD_ABOUT 1007

FAVICON ICON DISCARDABLE "favicon.ico"
LOGO BITMAP DISCARDABLE "ARTeam_Keygen2.bmp"

IDD_KEYGEN DIALOGEX 0,0,261,106
FONT 8,"MS Sans Serif"
STYLE 0x90000800
EXSTYLE 0x00000000
BEGIN
    CONTROL "#2002",IDC_IMAGE,"Static",0x5002020e,-7,-3,274,114,0x00000000
    CONTROL "About",IDC_ABOUT,"Button",0x54018000,30,80,47,17,0x00000000
    CONTROL "Copy",IDC_COPY,"Button",0x54018000,110,80,47,17,0x00000000
    CONTROL "Exit",IDC_EXIT,"Button",0x54018000,191,80,47,17,0x00000000
    CONTROL "",IDC_NAME,"Edit",0x50810081,71,9,164,13,0x00000000
    CONTROL "SuperCleaner 2.89 Keygen",IDC_SERIAL,"Edit",0x50810881,71,28,163,12,0x00000000
END

IDD_ABOUT DIALOGEX 0,0,151,103
FONT 8,"MS Sans Serif"
STYLE 0xd0000800
EXSTYLE 0x00000000
BEGIN
END


customkeygen.asm

.386
.model flat,stdcall
option casemap:none

include			windows.inc
include			kernel32.inc
include			user32.inc
include			ufmodapi.inc
includelib		kernel32.lib
includelib		user32.lib
includelib		ufmod.lib
includelib		winmm.lib

DlgProc			proto		:DWORD,:DWORD,:DWORD,:DWORD
FadeIn      		proto		:DWORD
FadeOut		    	proto		:DWORD
Generate		proto		:DWORD

.data
include chiptune.inc
xmSize equ $ - table
Hash1			dd		026h
Hash2			dd		034h
Hash3			dd		0ch
Hash4			dd		0eh
FormatControl		db		"%d-",0
EndFormatControl 	db		"%d",0
AboutTxt		db		"SuperCleaner 2.89 Keygen",10,13
            db		"~Coded by Goppit~",10,13
            db		"Release Date 23/02/06",0
AboutCap		db		"NFO",0

.data?
hInstance		HINSTANCE	?
Transparency		dd		?
NameBuffer		db		40 dup(?)
SerialBuffer		db		40 dup(?)
SerialSection		db		32 dup(?)

.const
IDD_KEYGEN		equ		1001
IDC_EXIT		equ		1002
IDC_COPY		equ		1003
IDC_ABOUT		equ		1004
IDC_NAME		equ		1005
IDC_SERIAL		equ		1006
ICON			equ		2001
LWA_ALPHA		equ		2
LWA_COLORKEY		equ		1
WS_EX_LAYERED		equ		80000h
DELAY_VALUE		equ		10

.code
start:
    invoke GetModuleHandle,NULL
    mov hInstance,eax
    invoke DialogBoxParam,hInstance,IDD_KEYGEN,NULL,addr DlgProc,NULL
    invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .if uMsg == WM_INITDIALOG
        invoke GetWindowLong,hWnd,GWL_EXSTYLE
        or eax,WS_EX_LAYERED
        invoke SetWindowLong,hWnd,GWL_EXSTYLE,eax
        invoke SetWindowPos,hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE+SWP_NOSIZE
        invoke LoadIcon,hInstance,ICON
        invoke SendMessage,hWnd,WM_SETICON,1,eax
        invoke GetDlgItem,hWnd,IDC_NAME
        invoke SetFocus,eax
        invoke uFMOD_PlaySong,addr table,xmSize,XM_MEMORY
        invoke FadeIn,hWnd
    .elseif uMsg==WM_LBUTTONDOWN
        invoke SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,lParam
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        .if ax==IDC_NAME
            shr eax,16
        .if ax==EN_CHANGE
            invoke Generate,hWnd
        .endif
    .elseif eax==IDC_ABOUT
        invoke MessageBox,hWnd,addr AboutTxt,addr AboutCap,MB_OK
    .elseif eax==IDC_COPY
        invoke SendDlgItemMessage,hWnd,IDC_SERIAL,EM_SETSEL,0,-1
        invoke SendDlgItemMessage,hWnd,IDC_SERIAL,WM_COPY,0,0
    .elseif eax==IDC_EXIT
        invoke	SendMessage,hWnd,WM_CLOSE,0,0
    .endif
    .elseif	uMsg== WM_CLOSE
        invoke FadeOut,hWnd
        invoke uFMOD_PlaySong,0,0,0
        invoke EndDialog,hWnd,0
    .endif
    xor eax,eax
    ret
DlgProc endp

FadeIn proc hWnd:HWND
    invoke ShowWindow,hWnd,SW_SHOW
    mov Transparency,75
@@:
    invoke SetLayeredWindowAttributes,hWnd,0,Transparency,LWA_ALPHA
    invoke Sleep,DELAY_VALUE
    add Transparency,5
    cmp Transparency,255
    jne @b
    ret
FadeIn endp

FadeOut proc hWnd:HWND
    mov Transparency,255
@@:
    invoke SetLayeredWindowAttributes,hWnd,0,Transparency,LWA_ALPHA
    invoke Sleep,DELAY_VALUE
    sub Transparency,5
    cmp Transparency,0
    jne @b
    ret
FadeOut endp

Generate proc hWnd:HWND
    invoke GetDlgItemText,hWnd,IDC_NAME,addr NameBuffer,40
    mov edi,offset NameBuffer
    invoke lstrlen,edi
    mov esi,eax
    xor ecx,ecx
    xor eax,eax
    test esi,esi
    jle NOINPUT
    mov edx,Hash1
@@:
    movsx ebx,byte ptr [eax+edi]
    add ebx,edx
    add ecx,ebx
    inc eax
    cmp eax,esi
    jl @b
    invoke wsprintf,addr SerialBuffer,addr FormatControl,ecx
    xor ecx,ecx
    mov eax,ecx
    mov edx,Hash2
@@:
    movsx ebx,byte ptr [eax+edi]
    imul ebx,edx
    add ecx,ebx
    inc eax
    cmp eax,esi
    jl @b
    invoke wsprintf,addr SerialSection,addr FormatControl,ecx
    invoke lstrcat,addr SerialBuffer,addr SerialSection
    xor ecx,ecx
    mov eax,ecx
    mov edx,Hash3
@@:
    movsx ebx,byte ptr [eax+edi]
    add ebx,edx
    add ecx,ebx
    inc eax
    cmp eax,esi
    jl @b
    invoke wsprintf,addr SerialSection,addr FormatControl,ecx
    invoke lstrcat,addr SerialBuffer,addr SerialSection
    xor ecx,ecx
    mov eax,ecx
    mov edx,Hash4
@@:
    movsx ebx,byte ptr [eax+edi]
    imul ebx,edx
    add ecx,ebx
    inc eax
    cmp eax,esi
    jl @b
    invoke wsprintf,addr SerialSection,addr EndFormatControl,ecx
    invoke lstrcat,addr SerialBuffer,addr SerialSection
    invoke SetDlgItemText,hWnd,IDC_SERIAL,addr SerialBuffer
    xor eax,eax
    ret
NOINPUT:
    invoke RtlZeroMemory,addr SerialBuffer,40
    invoke SetDlgItemText,hWnd,IDC_SERIAL,addr SerialBuffer
    xor eax,eax
    ret
Generate endp

end start


AddSection.rc

#define IDD_DLG1001 1001
#define IDC_STATIC1002 1002
#define ARIcon 2001
#define IDC_TARGET 1003
#define IDC_EXIT 1006
#define IDC_GO 1005
#define IDC_BROWSE 1004

ARIcon ICON DISCARDABLE "C:/Code Library/Projects/asm tut/PEValidate/favicon.ico"
1 24 DISCARDABLE "C:/Code Library/Projects/asm tut/PEValidate/XPVisualStyle.xml"

IDD_DLG1001 DIALOGEX 0,0,217,71
CAPTION "AddSection"
FONT 8,"MS Sans Serif"
STYLE 0x10cc0800
EXSTYLE 0x00000000
BEGIN
    CONTROL "Target:",IDC_STATIC1002,"Static",0x50000201,10,15,24,15,0x00000000
    CONTROL "",IDC_TARGET,"Edit",0x50010080,37,15,144,16,0x00000200
    CONTROL ". . .",IDC_BROWSE,"Button",0x50010000,187,15,17,16,0x00000000
    CONTROL "GO!",IDC_GO,"Button",0x50010000,37,43,51,16,0x00000000
    CONTROL "EXIT",IDC_EXIT,"Button",0x50010000,130,43,51,16,0x00000000
END


AddSection.asm

.386
.model	flat, stdcall
option	casemap :none

include		windows.inc
include		user32.inc
include		kernel32.inc
include		comdlg32.inc
includelib	user32.lib
includelib	kernel32.lib
includelib	comdlg32.lib

DlgProc			PROTO		:DWORD,:DWORD,:DWORD,:DWORD
Validate		PROTO		:DWORD
AddSection		PROTO		:DWORD

.data
strFilter		db			"Executable Files (*.exe, *.dll)",0,
                            "*.exe;*.dll",0,"All Files",0,"*.*",0,0
OpenError		db			"Unable to open target file",0
Invalid			db			"This is not a valid PE file!!",0
Success			db			"New section added successfully",0
NoSpace			db			"No space for new Section Header",0
Backup			db			".bak",0

;*********VALUES REQUIRED TO CONSTRUCT NEW SECTION*************
NewSecName		dd			"WEN."
NewSecSize		dd			100h
NewSecChar		dd			0E0000060h
KeepBkup		db			TRUE

.data?
ofn				OPENFILENAME	<>
hInstance		HINSTANCE	?
TargetName		db			512 dup(?)
hTarget			dd			?
hMapping		dd			?
pMapping		dd			?
pPEHeader		dd			?
Buffer			db			512 dup (?)

.const
IDD_MAIN		equ			1001
IDC_TARGET		equ			1003
IDC_BROWSE		equ			1004
IDC_GO 			equ			1005
IDC_EXIT 		equ			1006
ARIcon			equ			2001

.code
start:
    invoke	GetModuleHandle, NULL
    mov	hInstance, eax
    invoke	DialogBoxParam,hInstance,IDD_MAIN,0,addr DlgProc,0
    invoke	ExitProcess, eax

DlgProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    mov	eax,uMsg
    .if	eax==WM_INITDIALOG
        invoke	LoadIcon,hInstance,2001
        invoke	SendMessage,hWin,WM_SETICON,1,eax
    .elseif eax==WM_COMMAND
        mov	eax,wParam
        .if	eax==IDC_BROWSE
            mov ofn.lStructSize,SIZEOF ofn
            mov ofn.lpstrFilter,offset strFilter
            mov ofn.lpstrFile,offset TargetName
            mov ofn.nMaxFile,512
            mov ofn.Flags,OFN_FILEMUSTEXIST+OFN_PATHMUSTEXIST+\
                        OFN_LONGNAMES+OFN_EXPLORER+OFN_HIDEREADONLY
            invoke GetOpenFileName,addr ofn
            .if eax==TRUE
                invoke SetDlgItemText,hWin,IDC_TARGET,addr TargetName
                invoke RtlZeroMemory,addr TargetName,512
            .endif
        .elseif eax==IDC_GO
            invoke GetDlgItemText,hWin,IDC_TARGET,addr TargetName,512
            invoke lstrlen,addr TargetName
            .if eax!=0
                invoke Validate,addr TargetName
                .if eax==1
                    invoke AddSection,addr TargetName
                    .if eax==1
                        invoke MessageBox,hWin,addr Success,0,0
                    .elseif eax==-1
                        invoke MessageBox,hWin,addr NoSpace,0,0
                    .endif
                .elseif eax==-1
                    invoke MessageBox,hWin,addr Invalid,0,0
                .endif
            .endif
        .elseif eax==IDC_EXIT
            invoke SendMessage,hWin,WM_CLOSE,0,0
        .endif
    .elseif	eax==WM_CLOSE
        invoke	EndDialog,hWin,0
    .endif
    xor	eax,eax
    ret
DlgProc endp

Validate proc FileName:DWORD

;**************************************************************************************
; TAKES POINTER TO TARGET NAME, RETURNS 1 IF VALID, -1 IF NOT, 0 IF UNABLE TO OPEN FILE
;**************************************************************************************

LOCAL ReturnValue:DWORD
    invoke CreateFile,FileName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
    .if eax!=INVALID_HANDLE_VALUE
        mov hTarget,eax
        invoke CreateFileMapping,eax,0,PAGE_READONLY,0,0,0
        mov hMapping,eax
        invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0
        mov pMapping,eax
        .if [eax.IMAGE_DOS_HEADER.e_magic]==IMAGE_DOS_SIGNATURE
            add eax,[eax.IMAGE_DOS_HEADER.e_lfanew]
            .if [eax.IMAGE_NT_HEADERS.Signature]==IMAGE_NT_SIGNATURE
                mov ReturnValue,1
                jmp @EXIT
            .endif
        .else
            mov ReturnValue,-1
            jmp @EXIT
        .endif
@EXIT:
        invoke UnmapViewOfFile,pMapping
        invoke CloseHandle,hMapping
        invoke CloseHandle,hTarget
        mov eax,ReturnValue
        ret
    .else
        invoke MessageBox,0,addr OpenError,0,0
        xor eax,eax
        Ret
    .endif
Validate EndP

AddSection proc FileName:DWORD

;*********************************************************************************************
; TAKES POINTER TO TARGET NAME, RETURNS 1 IF SUCCESS, -1 IF NO SPACE, 0 IF UNABLE TO OPEN FILE
;*********************************************************************************************

LOCAL ReturnValue:DWORD
;*******MAKE BACKUP***************************************************
        invoke lstrcpy,addr Buffer,FileName
        invoke lstrcat,addr Buffer,addr Backup
        invoke CopyFile,FileName,addr Buffer,TRUE
;*******OPEN FILE MAP IN AND INCREASE SIZE****************************
    invoke CreateFile,FileName,GENERIC_READ+GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
    .if eax!=INVALID_HANDLE_VALUE
        mov hTarget,eax
        invoke GetFileSize,eax,0
        add eax,NewSecSize
        invoke CreateFileMapping,hTarget,0,PAGE_READWRITE,0,eax,0
        mov hMapping,eax
        invoke MapViewOfFile,eax,FILE_MAP_ALL_ACCESS,0,0,0
        mov pMapping,eax
;******CHECK ENOUGH ROOM FOR NEW ISH IN SECTION TABLE******************
        add eax,[eax.IMAGE_DOS_HEADER.e_lfanew]	;pointer to PE header in eax
        mov pPEHeader,eax
        movzx esi,[eax.IMAGE_NT_HEADERS.FileHeader.NumberOfSections];no of sections in ecx
        inc esi									;add 1
        imul esi,esi,40							;calc size of all ISHs
        add esi,0F8h							;add above to offset of sec table from PE header
        mov ebx,pMapping
        add esi,[ebx.IMAGE_DOS_HEADER.e_lfanew]	;add above to offset of PE header from beginning of file
        .if esi<[eax.IMAGE_NT_HEADERS.OptionalHeader.SizeOfHeaders]	;cmp offset of end of new ISH to offset of first section
;*******IF SO FILL NEW ISH**********************************************
            add esi,ebx							;make above offset into a pointer by adding base address
            sub esi,40
            mov edi,esi							;pointer to start of new ISH in edi
            sub esi,40							;pointer to start of last ISH in esi
            mov eax,NewSecName
            mov dword ptr [edi.IMAGE_SECTION_HEADER.Name1],eax	;write in Name1
            mov eax,NewSecSize
            mov [edi.IMAGE_SECTION_HEADER.Misc.VirtualSize],eax	;write in virtual size
            xor edx,edx											;align raw size of new sec
            mov ebx,pPEHeader
            mov ecx,[ebx.IMAGE_NT_HEADERS.OptionalHeader.FileAlignment]
            div ecx				;divides file alignment by raw size, quotient into eax, remainder into edx
            .if edx!=0			;if no remainder, ie already aligned then multiply back up
                inc eax			;if there is remainder, ie not aligned, then
            .endif				;add 1 to quotient and multiply back up (rounds up to alignment value)
            mul ecx
            mov [edi.IMAGE_SECTION_HEADER.SizeOfRawData],eax	;write in raw size
            mov eax,[esi.IMAGE_SECTION_HEADER.VirtualAddress]	;get RVA of last section
            add eax,[esi.IMAGE_SECTION_HEADER.Misc.VirtualSize]	;add virt size of last sec to get RVA of new sec
            xor edx,edx											;align RVA of new sec
            mov ecx,[ebx.IMAGE_NT_HEADERS.OptionalHeader.SectionAlignment]
            div ecx
            .if edx!=0
                inc eax
            .endif
            mul ecx
            mov [edi.IMAGE_SECTION_HEADER.VirtualAddress],eax	;write in RVA of new sec
            mov eax,[esi.IMAGE_SECTION_HEADER.PointerToRawData]	;get raw offset of last sec
            add eax,[esi.IMAGE_SECTION_HEADER.SizeOfRawData]	;add raw size
            xor edx,edx											;align raw offset
            mov ecx,[ebx.IMAGE_NT_HEADERS.OptionalHeader.FileAlignment]
            div ecx
            .if edx!=0
                inc eax
            .endif
            mul ecx
            mov [edi.IMAGE_SECTION_HEADER.PointerToRawData],eax	;write in raw offset of new sec
            mov edx,NewSecChar
            mov [edi.IMAGE_SECTION_HEADER.Characteristics],edx	;write in new sec characteristics
;*******UPDATE PE HEADER***********************************************
            mov eax,[edi.IMAGE_SECTION_HEADER.Misc.VirtualSize]	;get new sec virt size
            xor edx,edx											;align to section alignment
            mov ecx,[ebx.IMAGE_NT_HEADERS.OptionalHeader.SectionAlignment]
            div ecx
            .if edx!=0
                inc eax
            .endif
            mul ecx
            add eax,[edi.IMAGE_SECTION_HEADER.VirtualAddress]	;add RVA where new section begins in memory
            mov [ebx.IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage],eax	;write new size of image
            inc [ebx.IMAGE_NT_HEADERS.FileHeader.NumberOfSections];add 1 to no of sections
            mov ReturnValue,1
            jmp @EXIT
        .else
;*******IF NO SPACE THEN RESTORE FROM BACKUP AND DELETE BACKUP**********
            invoke UnmapViewOfFile,pMapping
            invoke CloseHandle,hMapping
            invoke CloseHandle,hTarget
            invoke DeleteFile,FileName
            invoke CopyFile,addr Buffer,FileName,FALSE
            invoke DeleteFile,addr Buffer
            mov ReturnValue,-1
            jmp @EXIT
        .endif
@EXIT:
;*******DECIDE WHETHER TO KEEP BACKUP AFTER SUCCESS*********************
        invoke UnmapViewOfFile,pMapping
        invoke CloseHandle,hMapping
        invoke CloseHandle,hTarget
        .if KeepBkup==FALSE
            invoke DeleteFile,addr Buffer
        .endif
        mov eax,ReturnValue
        ret
    .else
        invoke MessageBox,0,addr OpenError,0,0
        xor eax,eax
        Ret
    .endif
    Ret
AddSection EndP

end start


Accueil

Accueil CHM

Normal, dit comme ça,
vous ne risquez pas de comprendre.